Skip to content

Understanding the EZProxy/OverDrive connection

Disclaimer and intended audience

This document describes a one off, OverDrive style EZProxy integration that we maintain only for a small set of existing Statista customers who cannot use modern SSO options such as SAML or OIDC.

It is aimed at engineers, SSO specialists, and EZProxy administrators who need to understand how this legacy-compatible flow works end to end in order to support, debug, or gradually phase it out in favor of more standard integrations.

OverDrive Mode: Custom EZProxy Adventures with Statista

Before diving deep into the nitty-gritty details of this implementation, a little bit of history is important to understand the reasoning behind this decision.

In the beginning, our customers using EZProxy already for their authentication and authorization needs, relied on our IP based authentication to get access to Statista's premium content. Since, things have changed: customers have acquired proper identity providers for their infrastructures, Statista's focus has shifted toward a more data driven business model, and IP based login have become a nuance and a liability, granting anonymous, uncontrolled access to premium resources under the umbrella of a Statista Campus license acquired by the institutions.

With the sunset of IP based authentication, we encouraged our existing customers to migrate to better authentication alternatives such as SAML or OIDC. However, not every customer has the budget or willingness to invest in a proper IdP if they are already relying on EZProxy for that purpose. There's a small group of customers who fit this category and are considered high profile customers from a sales perspective. For these customers, OCLC offered a custom development they had previously done for OverDrive, which, as mentioned on our discussions with them: will fulfill our minimum requirements for a secure and acceptable authentication method.

Behind the Proxy: How EZProxy Works

Before we get into the specifics of our Statista setup, it helps to understand the role EZProxy plays as the middle layer between users, identity providers, and content platforms.

If you are not yet familiar with EZProxy, or need a quick refresher, please refer to the Understanding EZProxy section.

How the OverDrive-Style Solution Works

At a high level the OverDrive-style solution turns EZProxy into a dedicated authentication gateway: it authenticates the user, mints a stable opaque identifier, signs the request, and then gets out of the way so the user can talk directly to the vendor.

No content is proxied; only the authentication hop passes through EZProxy.

High level authentication flow

The core flow can be summarized like this: the user arrives at EZProxy, they verify their session and permissions, generate a persistent token, compute an authentication hash, and finally redirect them to Statista with all required parameters.

sequenceDiagram
    participant U as User
    participant E as EZProxy
    participant A as Library Auth System
    participant V as Vendor (Statista)

    U->>E: Request vendor access
    E->>A: Authenticate user (SAML/OIDC/LDAP/etc.)
    A-->>E: User identity confirmed
    E->>E: Verify session & group permissions
    E->>E: Generate/retrieve persistent token (ezproxy.tkn)
    E->>E: Build timestamp (UTC ISO 8601)
    E->>E: Compute SHA-1 auth hash (token | timestamp | secret)
    E-->>U: HTTP redirect with token, timestamp, hash, landing URL
    U->>V: Follow redirect to vendor endpoint
    V->>V: Validate hash and timestamp
    V-->>U: Grant access to premium content

Token generation and persistence

The solution relies on a persistent, opaque token that uniquely represents a user without exposing their real identity.

flowchart TD
    A["User identity<br/>(username, IdP attributes)"] --> B["Normalize username\n(e.g. lowercase)"]
    B --> C["Combine inputs:<br/>- token salt<br/>- normalized username<br/>- server identifier<br/>- service prefix (e.g. 'ods')<br/>- obscurity value"]
    C --> D[SHA-512 hash]
    D --> E[Take first 10 hex chars]
    E --> F["Prefix with service tag<br/>(e.g. 'ods' + 10 hex chars)"]
    F --> G[Store in ezproxy.tkn<br/>if new mapping]
    G --> H[Use as PatronID/token<br/>for vendor redirects]

Key properties of this token design are: deterministic for the same user and server, persistent via the ezproxy.tkn file, unique with collision checks, and opaque from the vendor’s perspective.

Because ezproxy.tkn is the single source of truth for these mappings, losing it effectively breaks all existing vendor-side accounts linked to those tokens.

Redirect and Hash Construction

To hand off a user to the vendor, EZProxy builds a signed redirect URL containing the token, a timestamp, and an authentication hash based on a shared secret.

flowchart TD
    A[Persistent token] --> B["Generate UTC timestamp<br/>(ISO 8601)"]
    B --> C["Concatenate:<br/> token | timestamp | shared secret"]
    C --> D["Compute SHA-1 hash<br/>(40 hex chars)"]
    D --> E["Build redirect URL:<br/>/BANGAuthenticate.dll?<br/> Action=ExternalAuth<br/> PatronID=token<br/> Timestamp=timestamp<br/> Hash=hash<br/> URL=landing_url<br/> (plus ILSName, LibraryID if needed)"]
    E --> F[Redirect user to vendor endpoint]

This simple concatenation hash (not HMAC) is a legacy requirement, but combined with session checks, group-based access control, and token obfuscation, it still forms part of a layered security approach.

Wiring OverDrive-Style Auth into Authi0

To make this OverDrive-style flow play nicely with our existing SSO stack, we let EZProxy do its thing (authenticate and mint a token), then hand everything over to Auth0 and our own services to finish the OpenID Connect flow on behalf of the user.

The end result is that Statista sees a regular Auth0-federated user, while the actual authentication story is driven by EZProxy and our ezproxy-auth-service behind the scenes.

remix_sso as the BANGAuthenticate endpoint

On the frontend of this integration, our remix-sso app exposes a /BANGAuthenticate.dll route that takes the place of the original vendor endpoint.

This route validates the incoming query parameters from EZProxy using a strict Zod schema, ensuring they match the OverDrive-style contract before we go anywhere near Auth0.

Once the parameters pass validation, remix-sso:

  • Encodes the resulting JSON object in base64.
  • Adds it as the access_type search parameter.
  • Appends connection=ezproxy-login so Auth0 uses the custom social connection wired to our ezproxy-auth-service.
sequenceDiagram
    participant U as User
    participant EZ as EZProxy
    participant RS as remix_sso (/BANGAuthenticate.dll)
    participant A0 as Auth0 (ezproxy-login)

    U->>EZ: Request Statista access
    EZ-->>U: Redirect to /BANGAuthenticate.dll<br/>with action, patron_id, timestamp, hash, ils_name
    U->>RS: GET /BANGAuthenticate.dll?...
    RS->>RS: Validate params with ezProxyAuthParamsSchema
    RS->>RS: Base64-encode validated JSON -> access_type
    RS-->>U: Redirect to Auth0<br/>connection=ezproxy-login&access_type=...
    U->>A0: Auth0 authorize request

Custom social connection and ezproxy-auth-service

In Auth0, we configured a custom social connection that points its OAuth endpoints to our ezproxy-auth-service lambda: /ezproxyauth/authorize and /ezproxyauth/oauthtoken.

Within Auth0, the access_type query parameter is mapped to ezproxy_claims, which becomes the payload our lambda receives and validates.

The ezproxy-auth-service is responsible for:

  • Parsing and validating the ezproxy_claims again (defensive validation).
  • Verifying the OverDrive-style hash using the shared secret.
  • Looking up the correct group account in a JSON configuration file encrypted with ejson.

The [EZProxy customers file] (https://github.com/CPE-Orga/platform-sso-services/blob/5ff844767956497300eb828eb90d34d13a2a532f/data/prod_ezproxy-domains.json) contains the shared secrets and additional customer data.

The shared secret for each domain is stored encrypted using the ejson library, and _groupAccountId identifies the group account that holds the Statista license in our users database.

Once the hash and claims are verified, the service resolves the actual user_id behind that group account and uses to generate the authorization code we return to Auth0.

Here, user.id is the internal identifier of the group account, and ezProxyAuthParams.patron_id keeps the link back to the EZProxy token so we can respect that identity across the flow.

sequenceDiagram
    participant RS as remix_sso
    participant A0 as Auth0
    participant EZS as ezproxy-auth-service
    participant DB as Platform DB

    RS->>A0: /authorize connection=ezproxy-login&access_type=...
    A0->>EZS: /ezproxyauth/authorize with ezproxy_claims
    EZS->>EZS: Validate ezproxy_claims & hash
    EZS->>DB: Lookup group account by _groupAccountId
    DB-->>EZS: user record (group account)
    EZS->>EZS: generateAuthorizationCode(clientId, clientSecret, user.id, patron_id)
    EZS-->>A0: Authorization code
    A0-->>RS: Redirect back with authorization code

From authorization code to JWT and Statista session

The /ezproxyauth/oauthtoken endpoint is the token side of this custom connection.

It validates the authorization code, looks up the same group account user, and issues a signed JWT that Auth0 will accept as the result of the social login.

The user object is built from our group account data, using a query to our legacy users database.

Auth0 consumes this JWT, creates (or retrieves) the corresponding federated user, and then continues with its standard flow: it issues its own id and access tokens and redirects back to the /callback endpoint in remix_sso.

Finally, remix_sso establishes the session, sets the necessary cookies, and redirects the browser to Statista with an authenticated user that now looks like any other Auth0 user from Statista’s perspective.

flowchart TD
    A[EZProxy token & claims] --> B[remix_sso /BANGAuthenticate.dll]
    B --> C["Auth0 custom social<br/>(ezproxy-login)"]
    C --> D[ezproxy-auth-service<br/>/ezproxyauth/authorize]
    D --> E[Issue authorization code<br/>for group account user]
    E --> F[ezproxy-auth-service<br/>/ezproxyauth/oauthtoken]
    F --> G["Signed JWT (user from group account)"]
    G --> H[Auth0 federated user & tokens]
    H --> I[remix_sso /callback<br/>creates session & cookies]
    I --> J[Redirect to Statista<br/>with authenticated user]

Why we did it this way

This design lets us keep EZProxy in the driver’s seat for authentication, while still presenting a clean, standards-based OIDC integration to Statista via Auth0.

By using opaque EZProxy tokens and group accounts, we avoid exposing individual user identities to Statista and still respect academia licensing models.

Reusing the OverDrive-style pattern also means we build on a flow that OCLC already supports and documents, instead of inventing a one-off protocol for a handful of high-profile customers.

Auth0’s custom social connection acts as a flexible bridge: it lets us wrap our proprietary EZProxy based authentication in a familiar OAuth2/OIDC envelope that our platform already know how to consume.

Finally, because we had no other option. This is a crutch we offer to our existing customers out of courtesy and should not be offered to any new customers

Resources

OCLC – OverDriveSite directive (configuration and qualifiers) Auth0 – Connect apps to generic OAuth2 authorization servers (generic/custom OAuth2 connection)