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:
- Parse all incoming parameters using
mapEzProxyParams, which normalises EZproxy's PascalCase parameter names (Action,PatronID,Timestamp,Hash,ILSName,URL) into lowercase camelCase fields. - Validate with Zod via
ezProxyAuthParamsSchema. The five identity parameters are required;urlis optional. An invalid request returns400 Bad Requestimmediately. - Build the Auth0 redirect URL using
buildEZProxyRedirectUrl. This encodes the identity parameters (action,patron_id,timestamp,hash,ils_name) as a base64 JSON blob inaccess_type, and appendsconnection=ezproxy-login. Theurlfield is not included in this payload — it is handled separately. - Append
__sso_redirect(viabuildLoginRequestUrl). If theURLparameter was present and is a valid absolute URL, the route transforms it to a path-relative value with an__sso_originquery parameter and appends it as__sso_redirecton the Auth0 redirect URL. If theURLparameter is absent or malformed,__sso_redirectis simply not added and authentication proceeds normally. - 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_redirectto be omitted — the request is not rejected. - The path and existing query parameters are preserved.
__sso_originis set to the URL's origin, overwriting any existing__sso_originvalue 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_redirectset 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
URLis absent,__sso_redirectis not present on the Auth0 redirect URL. - When
URLis an invalid string (e.g.not-a-valid-url),__sso_redirectis 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.
mapEzProxyParamsnormalisation from PascalCase EZproxy parameters.buildEZProxyRedirectUrl— verifiesaccess_typeencoding and thaturlis excluded from the payload.buildLoginRequestUrl— verifies__sso_redirectis set correctly, that existing query params are preserved, that spoofed__sso_originvalues are overwritten, and that invalid or absent URLs are handled gracefully.- The
loaderfunction end-to-end (mocking the authenticator), including deep-link URL propagation and the absence ofurlin theaccess_typepayload.
See Also
- Deep Linking Overview
- EZProxy/OverDrive Overview — the full OverDrive architecture including the Lambda, the Auth0 connection, and hash validation.
- Understanding EZProxy — how EZproxy works as a middleware solution.