OAuth server setup
How to configure your OAuth 2.0/OIDC server for Plaid Core Exchange
Quick reference
| Component | Requirement | Where to find details |
|---|---|---|
| Discovery endpoint | /.well-known/openid-configuration | Configuration requirements |
| JWKS endpoint | Public HTTPS endpoint | JWKS section |
| Consistency key | 7+ character unique ID | Unique user identifier |
| Credentials | Client ID + Secret | Credentials section |
| Required scopes | openid, offline_access | Well-known configuration |
OAuth server setup
Your OAuth 2.0 server needs to issue standards-compliant tokens. Plaid strongly recommends implementing OpenID Connect with the OIDC Discovery Specification. OIDC enables a better user experience for returning users, centralized consent management, and optimal API performance through the ID token's sub field. Plain OAuth 2.0 is supported but requires providing the unique user identifier via the /customers/current endpoint instead.
For a working reference you can run locally, see FDX Explorer and linked source code on the Authentication overview.
Implementation options
Use an existing identity provider - Okta, Auth0, Ping Identity, Azure AD, or AWS Cognito all work, and provide the fastest implementation path.
Plaid recommends Okta's Plaid integration.
Build your own - Use an OIDC-certified library. This option gives you more control but takes more time.
Don't roll your own OAuth implementation from scratch. Custom, non-standard implementations create headaches for everyone. Stick to certified tools and established identity providers.
Configuration requirements
| Configuration | Notes |
|---|---|
| Server domain | Pick a domain for your OAuth server, usually something like auth.yourbank.com or yourbank.okta.com. Keep it separate from your API server. HTTPS is required. |
| Discovery endpoint | Plaid pulls your server config from /.well-known/openid-configuration. If your domain is https://auth.firstplatypusbank.com, Plaid will hit https://auth.firstplatypusbank.com/.well-known/openid-configuration. Make sure it returns the details in the next section, and that it is reachable from the public internet. If the Validator reports Error getting OIDC configuration, see Validator end-to-end failures. |
Well-known configuration
Your /.well-known/openid-configuration endpoint is public (no auth required). When Plaid hits it, the response should include at minimum these values:
| Property | Description | Required | Type |
|---|---|---|---|
authorization_endpoint | Where users authenticate. This endpoint hosts your login page. Must support TLS and be publicly accessible. | Yes | URL |
token_endpoint | Where Plaid exchanges authorization codes for access tokens. Backend-to-backend only. | Yes | URL |
userinfo_endpoint | Where Plaid retrieves the user identifier. Required for OIDC. Plain OAuth 2.0 implementations that do not use OIDC must instead provide the identifier via /customers/current. | Yes (OIDC) | URL |
token_endpoint_auth_methods_supported | How Plaid authenticates when requesting tokens. If omitted, Plaid defaults to basic authentication. | No | Array of strings |
response_types_supported | Supported response types. Must include code. | Yes | Array of strings |
scopes_supported | For OIDC implementations, must include openid and offline_access at a minimum. Plain OAuth 2.0 only requires offline_access. See details below. | Yes | Array of strings |
Scope details:
openid- Enables OIDC features. Lets Plaid read the user identifier from the ID token, enabling returning user experience, optimized API traffic, and centralized consent management. (If this scope is unavailable, Plaid will query/customers/currentinstead, but OIDC-specific features won't be available.)offline_access- Gives Plaid a refresh token to fetch data in the background. Required for use cases that need data access beyond the initial OAuth session, such as balance checks for stored bank accounts (recurring transfers, saved payment methods), transaction subscriptions, and ongoing data updates.
FDX scopes: Plaid may also request FDX data-scope values in the scope parameter (for example fdx:accountbasic:read or fdx:transactions:read) to describe the categories of data it's accessing:
| Scope | Data category |
|---|---|
fdx:accountbasic:read | Basic account information |
fdx:accountdetailed:read | Detailed account information |
fdx:paymentsupport:read | Full account number and payment network routing number |
fdx:customercontact:read | Customer contact information |
fdx:investments:read | Investment holdings and transactions |
fdx:statements:read | Account statements |
fdx:transactions:read | Transaction history |
FDX deprecated fdx:accountpayments:read in favor of fdx:paymentsupport:read in FDX 6.1.0, and will remove it in FDX 7. Plaid requests fdx:paymentsupport:read.
Your OAuth server does not need to enforce access control per FDX scope. Plaid requests them descriptively, and it's fine to grant a token every FDX scope your implementation supports without gating individual endpoints on them.
Example
curl -X GET 'https://auth.firstplatypusbank.com/.well-known/openid-configuration'{
"authorization_endpoint": "https://auth.firstplatypusbank.com/oauth2/v1/authorize",
"token_endpoint": "https://auth.firstplatypusbank.com/oauth2/v1/token",
"userinfo_endpoint": "https://auth.firstplatypusbank.com/oauth2/v1/userinfo",
"token_endpoint_auth_methods_supported": ["client_secret_basic"],
"response_types_supported": ["code"],
"scopes_supported": ["openid", "offline_access"]
}What to validate
When Plaid (or your own tooling) fetches your well-known configuration, confirm:
- All endpoint URLs use HTTPS, resolve via DNS, and are reachable from the public internet with valid Production TLS certificates.
- The endpoint returns HTTP 200 with
Content-Type: application/jsonand well-formed JSON. - Required properties in the table above are present and correctly populated (including
authorization_endpoint,token_endpoint,response_types_supportedwithcode, andscopes_supportedwithoffline_accessand, for OIDC,openid). - For OIDC,
jwks_uriis present, publicly accessible, and returns at least one signing key in a valid JWKS document.
Note for multi-institution deployments: If you serve multiple financial institutions, the sub field in the ID token must be unique per institution (but doesn't need to be unique across all institutions).
Unique user identifier (consistency key)
Plaid needs a stable identifier for each user, something that never changes, is unique across your user base, and stays consistent when that user connects multiple apps. This identifier (often called the "consistency key") enables Plaid to:
- Show users all their active connections in one place (my.plaid.com)
- Maintain a user-level view in Consent management
- Optimize API traffic by recognizing returning users
- Enable the returning user experience
Requirements
The identifier must be:
- Stable - Never changes for the duration of the user's relationship with you
- Unique - No two users share the same identifier
- Consistent - Same value across all apps a user connects to via Plaid
- Non-sensitive - Should not expose personally identifiable information (avoid SSN, email, phone)
- Sufficient length - Minimum seven characters
In practice, the best option is usually an internal customer identifier from your core system (for example, a GUID or numeric customer ID) as long as it is never reused, never changes, and does not expose login names or account numbers. Avoid using online banking usernames, email addresses, phone numbers, or account numbers as the identifier.
How to provide it
You have three options. Plaid tries them in this order and uses the first one available:
subfield in the ID token (recommended) - Include the user identifier as thesubclaim when issuing theid_tokenduring token exchange. Plaid uses this whenever you return anid_tokenand publish ajwks_uri, since it needs the key set to verify the token's signature. No extra request is needed beyond fetching your keys, which Plaid caches.userinfoendpoint - Return the identifier as thesubfield in the response from youruserinfo_endpoint. Plaid uses this when there's no ID token or nojwks_uri, and it costs one additional request to your authorization server per login./customers/currentendpoint - Return the identifier ascustomerIdin the Core Exchange/customers/currentresponse. Plaid uses this only when neither of the above is available. It's the least preferred option because it isn't part of OIDC, it makes login depend on your FDX API being reachable, and it forgoes the OIDC features described above.
Most integrations use option 1. Including the sub field in the ID token is the standard OIDC approach and requires no additional endpoint calls.
Selection happens once, based on what you've configured, and does not use a fallback flow. For example, publishing a jwks_uri and returning an id_token commits you to option 1. If Plaid can't verify your id_token against your JWKS, it reports that verification error and stops; it will not fall back to options 2 or 3. So a login failing in an integration that publishes a jwks_uri indicates an error with your keys or your token signing, not with the userinfo or /customers/current endpoints.
Whichever option you use, the value must satisfy the requirements above. A value shorter than seven characters, or absent entirely, fails the login with an invalid consistency key error rather than falling through to another option.
Example
When Plaid requests a token, your /token endpoint responds with an id_token that includes:
{
"access_token": "agstynmdygjdghabrgraeh...",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "dhcsrtjsrgayvkdisfdgntshstu..."
}{
"sub": "user_12345678",
"iss": "https://auth.firstplatypusbank.com",
"aud": "dc1fe34ae9e5e98147f2fd76060016a4",
"exp": 1672444800,
"iat": 1672441200
}The sub value (user_12345678) is the consistency key. It's 13 characters, unique to this user, and will never change.
JWKS (JSON Web Key Set)
Only OIDC implementations that issue ID tokens need to expose a JWKS endpoint. Plain OAuth 2.0 implementations don't need to provide one.
JWKS is how Plaid verifies your tokens are valid. The jwks_uri points to your public keys. Plaid uses them to verify the digital signatures on JWTs, such as the id_token. Plaid automatically fetches and caches your JWKS.
{
"keys": [
{
"kty": "RSA",
"use": "sig",
"kid": "ABCD1234",
"n": "4MZuI7r3F5y...",
"e": "AQAB",
"alg": "RS256"
}
]
}Keep your JWKS healthy:
- Host it over HTTPS (non-negotiable).
- Rotate signing keys at least annually, or immediately if compromised.
- Include a
kid(Key ID) in every key for quick lookup. - Use strong algorithms: RSA-2048 or better (
RS256,ES256, etc.).
Key rotation process
Rotating JWKS keys without breaking Plaid's token verification:
Publish the new key - Add your new key to the JWKS with a unique
kid. Keep the old key in the response.Wait for cache refresh - Plaid caches JWKS for up to 10 minutes to reduce load on your endpoint. Wait at least that long before proceeding to ensure Plaid has fetched and cached the new keys.
Start signing with new key - Update your ID tokens to use the new
kidin the JWT header. Plaid will use the cached JWKS to verify tokens signed with either the old or new key.Monitor for errors - Watch for signature validation errors (
invalid_signature,kid_not_found) in your logs and Plaid's error reports. Monitor for a period that covers your typical traffic patterns before proceeding.Remove the old key - After confirming no errors over a reasonable monitoring period, remove the old key from your JWKS. Plaid will remove the old key from its cache the next time it fetches your JWKS (within 10 minutes).
JWKS caching behavior:
- Cache duration: Up to 10 minutes
- Fetch trigger: Cache miss or expiration
- During rotation: Both old and new keys remain cached and valid during the transition period
Emergency rotation: If a key is compromised, rotate immediately. Coordinate with your Plaid contact to minimize disruption. Be prepared for some token validation failures during emergency rotations.
Recommended schedule: Annually at a minimum. Schedule rotations during low-traffic periods when possible.
Credentials
Plaid needs credentials to authenticate when requesting access tokens. You'll issue us a client ID (public identifier) and client secret (password).
Client ID
The client ID is public, but don't make it easy to guess. Length: 8-256 characters.
How to create one
Generate a random string using your language's cryptographically secure random number generator (CSPRNG), then encode as hexadecimal. Target 16 random bytes, which produces a 32-character hex string.
import crypto from 'crypto';
const randomString = crypto.randomBytes(16).toString('hex');
console.log(randomString);
Client secret
The client secret is Plaid's password. Treat it like one. Follow these security best practices:
- Generate it securely - Use a CSPRNG (cryptographically secure pseudo random number generator). Don't use UUIDs, as many libraries leak timestamps or MAC addresses.
- Never store it in plain text - Encrypt or hash it. Always.
- Make it visually distinct - Different format from the client ID helps prevent copy/paste errors.
Length: 8-256 characters.
How to generate one
Generate a 256-bit value using your language's CSPRNG, then encode as hexadecimal. This produces a 64-character hex string.
import crypto from 'crypto';
const randomString = crypto.randomBytes(32).toString('hex');
console.log(randomString);
Token expiration
Configure your token lifetimes to balance security with usability.
Access tokens: 15 minutes
Short-lived access tokens limit exposure if a token is compromised. Plaid checks expiration before each API call and automatically refreshes when needed.
Refresh tokens: 13+ months
Compliance requires users to reauthorize every 12 months, which Plaid handles automatically. Rotating refresh tokens or setting their expiration to 13+ months ensures they remain valid through this cycle. The extra buffer (13+ months is common) accounts for high-traffic periods like tax season, when users may not reauthorize immediately.
Setup checklist
- OAuth server configured with OIDC support
- Discovery endpoint returns valid configuration
- JWKS endpoint publicly accessible with valid keys
- Client ID and secret generated securely
- Consistency key implemented (via
sub, userinfo, or/customers/current) - TLS certificates valid (not self-signed for Production)
- Token expiration configured (15 min for access, 13+ months for refresh)
- All endpoints are tested and responding correctly