Skip to content

Deep Linking with EZproxy (OverDrive Mode)

Part of the Deep Linking series. This page covers the EZproxy OverDrive authentication path. If you have not read the main deep-linking overview yet, start there first — it explains what deep linking is, how it works at a high level, and where each authentication method stands.

Context: Our EZproxy Setup Is Not a Content Proxy

The classic EZproxy mental model is a content proxy — every outbound request from the user passes through the EZproxy server, which rewrites URLs and adds the institution's IP address. That model does not apply here.

In our setup, EZproxy is a pure authentication gateway. It authenticates the user against their institution's identity provider, mints an opaque persistent token, signs the request with a shared secret, and redirects the user directly to our application. Content delivery is never EZproxy's concern — it just hands us a verified identity and a destination.

The mechanism is the OverDrive directive in EZproxy's config.txt. When configured with OverDriveSite, EZproxy redirects authenticated users to our /BANGAuthenticate.dll endpoint with the following query parameters:

/BANGAuthenticate.dll?Action=ExternalAuth&PatronID={token}&Timestamp={ts}&Hash={hmac}&ILSName={ils}&URL={landing_url}

That last parameter — URL={landing_url} — is where deep linking enters the picture. It is the destination page the user originally requested, carried by EZproxy so our application can land them there after authentication.

For the full OverDrive architecture (the Lambda, the Auth0 custom social connection, the hash validation) see EZProxy/OverDrive Overview.

How Deep Linking Is Implemented

Our /BANGAuthenticate.dll route in remix-sso handles the URL parameter as part of its normal request processing. The mechanism differs from the OpenAthens path: instead of writing a __sso_redirect cookie, the route passes the deep-link destination as a query parameter on the Auth0 redirect URL.

The reason the two paths differ is the shape of the authentication round-trip. In the OpenAthens flow, oa-deeplink.ts redirects the browser to connect.openathens.net — a genuinely external service that our server has no control over. The destination must be stored somewhere our callback can recover it regardless of what happens in between, which is exactly what an HTTP-only cookie on our domain provides. In the EZProxy flow, external authentication is already complete before BANGAuthenticate.dll is invoked. The route's only remaining job is to call authenticator.login() and construct the Auth0 /authorize redirect. Auth0 carries the state parameter through its own round-trip and reflects it back to our callback intact — so __sso_redirect can travel as a URL parameter on that redirect without needing to be persisted in a cookie at all.

The flow inside BANGAuthenticate.dll is:

  1. Parse all incoming parameters using mapEzProxyParams, which normalises EZproxy's PascalCase parameter names (Action, PatronID, Timestamp, Hash, ILSName, URL) into lowercase camelCase fields.
  2. Validate with Zod via ezProxyAuthParamsSchema. The five identity parameters are required; url is optional. An invalid request returns 400 Bad Request immediately.
  3. Build the Auth0 redirect URL using buildEZProxyRedirectUrl. This encodes the identity parameters (action, patron_id, timestamp, hash, ils_name) as a base64 JSON blob in access_type, and appends connection=ezproxy-login. The url field is not included in this payload — it is handled separately.
  4. Append __sso_redirect (via buildLoginRequestUrl). If the URL parameter was present and is a valid absolute URL, the route transforms it to a path-relative value with an __sso_origin query parameter and appends it as __sso_redirect on the Auth0 redirect URL. If the URL parameter is absent or malformed, __sso_redirect is simply not added and authentication proceeds normally.
  5. Hand off to the authenticator, which follows the Auth0 redirect and continues the OIDC flow.

URL Transformation

The target URL is transformed using the same logic as the OpenAthens path:

// Input:  https://www.statista.com/statistics/269025/worldwide-mobile-app-revenue-forecast/
// Output: /statistics/269025/worldwide-mobile-app-revenue-forecast/?__sso_origin=https://www.statista.com

Specifically:

  • The URL is parsed with new URL(...). Anything that is not a valid absolute URL causes the warning to be logged and __sso_redirect to be omitted — the request is not rejected.
  • The path and existing query parameters are preserved.
  • __sso_origin is set to the URL's origin, overwriting any existing __sso_origin value in the incoming URL to prevent spoofing.
  • Only the path-relative result is stored, so the OIDC callback receives a safe, application-scoped target.

What Happens When URL Is Absent or Invalid

Scenario Outcome
URL not present in request __sso_redirect not added; user lands on homepage
URL present but not a valid URL Warning logged; __sso_redirect not added; user lands on homepage
URL present and valid __sso_redirect appended to Auth0 redirect URL

There is no fallback cookie written and no 400 returned for a missing or malformed URL — authentication always proceeds.

The Full Flow

sequenceDiagram
    actor User
    participant EZP as EZproxy<br/>(OverDrive mode)
    participant BANG as remix-sso<br/>/BANGAuthenticate.dll
    participant Auth0 as Auth0<br/>(ezproxy-login connection)
    participant Lambda as ezproxy-auth-service<br/>(Lambda)
    participant OIDC as remix-sso<br/>/sso/callback
    participant Target as Target Resource Page

    User->>EZP: Clicks deep link (institution portal / discovery system)
    EZP->>EZP: Authenticates user against institution IdP
    EZP->>BANG: GET /BANGAuthenticate.dll?Action=ExternalAuth&PatronID=…&Hash=…&ILSName=…&URL={landing_url}
    BANG->>BANG: Validate params (Zod schema — url is optional)
    BANG->>BANG: Transform URL → path-relative value with __sso_origin
    BANG->>Auth0: 302 → Auth0 /authorize?access_type=…&connection=ezproxy-login&__sso_redirect={path}
    Auth0->>Lambda: Invoke ezproxy-auth-service to verify claims
    Lambda-->>Auth0: Claims verified, issue authorization code
    Auth0->>OIDC: 302 → /callback?code=…
    OIDC->>OIDC: Token exchange, validate, establish session
    OIDC->>OIDC: Read __sso_redirect from session / request context
    OIDC->>User: 302 → target resource path ✓
    User->>Target: Lands on the specific page

Parameter Reference

All parameters arrive as query parameters on the GET /BANGAuthenticate.dll request. EZproxy uses PascalCase; the route normalises them internally.

EZproxy parameter Internal field Required Description
Action action Yes Must be the literal string "ExternalAuth".
PatronID patron_id Yes Opaque token minted by EZproxy. Must match /^ods[a-fA-F0-9]{10}$/.
Timestamp timestamp Yes ISO 8601 datetime string; used in HMAC verification downstream.
Hash hash Yes 40-character hex HMAC; verified by the Lambda function.
ILSName ils_name Yes Identifies the institution's integrated library system. Must be non-empty.
URL url No The destination page the user originally requested. Drives deep linking.

The url field is the only optional parameter. All others being absent or malformed will cause a 400 Bad Request to be returned immediately.

Security Notes

URL is not passed to Auth0/Lambda in the access_type payload. The base64-encoded identity blob only contains action, patron_id, timestamp, hash, and ils_name. This ensures the Lambda function's signature verification is not affected by the deep-link destination.

__sso_origin spoofing is prevented. If an incoming URL already contains a __sso_origin query parameter (e.g. from a crafted link), it is overwritten with the parsed origin of the URL itself. A caller cannot inject an arbitrary origin value.

Invalid URLs are silently dropped. The URL parameter is user-controlled input from EZproxy. Parsing failures are logged as warnings and the parameter is ignored; the request is never rejected because of a bad deep-link URL.

Testing

You can simulate an EZproxy redirect locally by constructing a request with the expected parameters:

http://localhost:3000/BANGAuthenticate.dll?Action=ExternalAuth&PatronID=odsabcdef1234&Timestamp=2024-01-01T00%3A00%3A00.000Z&Hash=1234567890123456789012345678901234567890&ILSName=StatistaLibrary&URL=https%3A%2F%2Fwww.statista.com%2Fstatistics%2F269025%2F

Check that:

  • The Auth0 redirect URL carries __sso_redirect set to the expected path-relative value (e.g. /statistics/269025/?__sso_origin=https%3A%2F%2Fwww.statista.com).
  • After a full round-trip, the user lands on the correct page.
  • When URL is absent, __sso_redirect is not present on the Auth0 redirect URL.
  • When URL is an invalid string (e.g. not-a-valid-url), __sso_redirect is not set and no error is thrown.

The test coverage lives in app/routes/__tests__/BANGAuthenticate[.]dll.test.ts and covers:

  • Schema validation for all required and optional fields.
  • mapEzProxyParams normalisation from PascalCase EZproxy parameters.
  • buildEZProxyRedirectUrl — verifies access_type encoding and that url is excluded from the payload.
  • buildLoginRequestUrl — verifies __sso_redirect is set correctly, that existing query params are preserved, that spoofed __sso_origin values are overwritten, and that invalid or absent URLs are handled gracefully.
  • The loader function end-to-end (mocking the authenticator), including deep-link URL propagation and the absence of url in the access_type payload.

See Also