Plaid logo
Core Exchange
ALL DOCS

Authentication

  • Overview
  • Planning your integration
  • OAuth server setup
  • Implementing the OAuth flow
  • App2App
Open nav
Core Exchange
Plaid.comGet Started

Implementing the OAuth flow

Step-by-step guide to the Authorization Code Grant with OIDC

Prerequisites: Before implementing the OAuth flow, ensure you have your OAuth server configured, client credentials generated, JWKS endpoint accessible, and consistency key implementation chosen.

Flow at a glance

Flow at a glance
User -> Plaid -> Your /authorize -> User authenticates -> Your /token -> Plaid validates -> API access
       (redirect)                  (login + 2FA)         (code exchange)  (JWKS check)

The OAuth flow, step by step

The Authorization Code Grant follows a five-step process. This guide describes the OIDC flow. Plain OAuth 2.0 follows the same flow but without the ID token validation step.

Screen placement

Under regulatory requirements, the data recipient (or its platform) is responsible for obtaining user authorization. Plaid fulfills this obligation on behalf of apps via Plaid Link screens, divided as follows:

Data Provider hosts (your responsibility):

  • Authentication screen - Users log in with their credentials on your domain.
  • Account selection (optional) - If you are not using Plaid account selection, you may host account selection on your domain. This approach works but adds friction and limits the user experience for returning users, so Plaid does not recommend it for most integrations.

Plaid hosts (handled by Plaid Link, recommended):

  • Data transparency messaging - Clear identification of which app is requesting access and what data will be shared.
  • Account selection - User selects which accounts to share in Plaid Link after authenticating with you. Plaid recommends this model as it provides the best user experience for returning users.

Keep the flow streamlined. Beyond the authentication screen (and, if you choose DP-owned account selection, that screen), avoid inserting additional pages into the end-to-end linking flow. Every extra screen increases drop-off.

The five steps:

  1. Plaid redirects your user to authenticate
  2. User logs in and authorizes access
  3. You send Plaid an authorization code
  4. Plaid exchanges the code for tokens
  5. Plaid verifies the ID token and accesses your API

Step 1 - Plaid redirects the end user

When a user starts linking their account, Plaid redirects them to your authorization endpoint (as defined in your well-known config). The redirect includes these query parameters:

Query parameters

Query parameterDescriptionValue
response_typeA string, indicating the type of response Plaid expects.Set to: code
redirect_uriWhere Plaid expects your organization to redirect back to once the user completes all authentication steps.https://cdn.plaid.com/link/v2/stable/oauth.html
scopeA set of strings indicating the set of scopes Plaid requests access to.Will be set to openid and offline_access, at minimum
client_idThe client ID you issued to Plaid.A client ID
stateYour organization will return the same string when redirecting to the redirect_uri.An opaque string
promptSpecifies the type of authentication prompt the server will send to the end user.Set to login
code_challengeOptional: This value is required when using PKCE.The code_verifier, either as a Base64URL-encoded hash or in plain text
code_challenge_methodOptional: This value is required when using PKCE.Indicates the format of the code_challenge. May be either: S256 (hashed by SHA-256 and Base64URL-encoded) or plain (in plain text)
Example redirect
https://auth.firstplatypus.com/oauth2/v1/authorize?response_type=code
&client_id=dc1fe34ae9e5e98147f2fd76060016a4
&redirect_uri=https%3A%2F%2Fcdn.plaid.com%2Flink%2Fv2%2Fstable%2Foauth.html
&state=v2.9f77edf0-a328-4501-9528-4a5f460cf770.0.0
&scope=openid%20offline_access%20accounts%20transactions
&prompt=login
&code_challenge=4dKRcTlKg7PbBxYokEH5gbfwfXcUdvDVYVdFZniVq4s
&code_challenge_method=S256

This page will be requested directly by the user's device.

Step 2 - The user completes all authentication steps

At this step, the user authenticates with your system, typically username/password plus 2FA.

Two-factor authentication (2FA)

Plaid requires 2FA for Core Exchange connections. Users must complete a second authentication factor beyond username and password. 2FA aligns with industry security standards and reduces the risk of account takeover.

Use industry-standard 2FA methods such as one-time codes (SMS, email, authenticator apps), push notifications, biometric authentication, or hardware security keys. For mobile apps, biometric authentication provides the best user experience.

2FA communication requirements:

When sending 2FA prompts (SMS, email, push notification), the message must:

  • Be clear about which app is requesting access - Include the app name from Plaid's authorization request
  • Look legitimate - Use your standard templates and sender information to avoid appearing like phishing
  • Be timely - Deliver within seconds of the authentication attempt
Example SMS
First Platypus Bank: Verify connection to Venmo.
Your code: 123456
If you didn't request this, contact us immediately.
Example email
Subject: Verify connection to Copilot

Hi [Name],

Venmo is requesting access to your First Platypus Bank account.

Your verification code: 123456

This code expires in 10 minutes.

If you didn't request this, please contact us immediately.

Mobile authentication

For mobile apps, enable App2App and biometric authentication. Your users will thank you.

User cancellation

If the user cancels, handle it as an error and send them back to Plaid with the right error code.

Step 3 - You create an authorization code and send it to Plaid

After the user successfully authenticates, generate a temporary authorization code and redirect the user's browser back to Plaid's redirect_uri. Include these query parameters:

Query parameterDescription
codeThe temporary authorization code. Plaid will exchange this for an access token in the next step.
stateThe state parameter from step 1. Plaid verifies that the two values match.

Example of your response

Example of your response
https://cdn.plaid.com/link/v2/stable/oauth.html?code=1284918391
&state=eyJvYXV0aF9zdGF0ZV

Step 4 - Plaid requests an access token

Now, Plaid calls your token_endpoint to exchange the authorization code for tokens. This is a backend-to-backend call (no user involved), authenticated using the client ID and secret you issued.

The auth method comes from your well-known config. If you specified client_secret_basic, Plaid will send a basic auth header.

Request body format: application/x-www-form-urlencoded. Plaid sends these parameters:

Body parameters

Body parameterDescriptionValue
promptSpecifies the type of a prompt the server will send to the end user.Will be set to: consent
grant_typeThe type of grant Plaid is exchanging for an access token. (In this case, an authorization code.)Will be set to: authorization_code
codeThe temporary authorization code Plaid is exchanging for the access token.The code you sent to Plaid in the previous step
redirect_uriWhere Plaid expects your organization to redirect back to once the user completes all authentication steps.https://cdn.plaid.com/link/v2/stable/oauth.html
scopeThe set of possible scopes.An array of scopes, for example: [offline_access, openid, accounts, transactions, identity]
audienceOptional: Defines who the token is for (the resource server that will consume the token). Plaid will send this value if this information is available.A string, for example: https://api.firstplatypusbank.com
code_verifierOptional: This value is only required when using PKCE.The code_verifier, created before starting the authentication flow
Plaid request example
curl --request POST 'https://auth.firstplatypusbank.com/oauth2/v1/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header "Authorization: Basic YzVhNTI0NWIwNjJiZjg0MjBkMTFhYjQzNjFiMjhhMTU6clZYWU9vUVM0ckhVRzc5bl80OGFs" \
--data-raw '{
    "prompt": "login",
    "grant_type": "authorization_code",
    "code": "1284918391",
    "redirect_uri": "https://cdn.plaid.com/link/v2/stable/oauth.html",
    "scope": ["offline_access", "accounts"]
}'

Validate that the client_id, client_secret, code, and redirect_uri all match what you expect. If everything checks out, send Plaid the access_token, id_token, and refresh_token. All three are required to access your API.

Response parameters

PropertyDescription
access_tokenAn opaque string (likely a JWT structured according to the OAuth2 specification). Plaid will present this string as a bearer token to all requests made to your Core Exchange API. The access_token encodes the user's identity and the scope of access granted.
expires_inThe lifetime of the access token, in seconds. Typically 15 minutes (900 seconds). Plaid checks for expiration before using an access token. If the access token has expired, Plaid will use the refresh token to request a new access token. If your organization expires the token before the stated expiration date, Plaid expects to receive a 401 response with an error code of "602 not authorized".
id_tokenAn OIDC ID token. Plaid only reads the sub field from this token. In a deployment with multiple financial institutions, the sub field must be unique to each financial institution. (It doesn't need to be unique to the user across all financial institutions.)
refresh_tokenAn opaque string (likely a JWT) that Plaid can use to request a new access token. Plaid will use this to fetch data periodically long after the original access token expires. See the refresh flow section for more information. Recommended expiration: 13+ months. Compliance requires reauthorization every 12 months, which Plaid handles automatically. Setting refresh token expiration to 13+ months ensures tokens remain valid through the reauthorization cycle. Don't use inactivity timeouts; some use cases involve infrequent API calls that shouldn't cause the connection to be invalidated.
Example of your response
{
  "access_token": "agstynmdygjdghabrgraeh...",
  "expires_in": 900,
  "id_token": "snsyjrhvjdtvyjvsgcegaethstj...",
  "refresh_token": "dhcsrtjsrgayvkdisfdgntshstu..."
}

Step 5 - Plaid verifies the ID token

This step only applies to OIDC implementations. Plain OAuth 2.0 implementations skip this step since there's no ID token to validate.

After receiving the id_token, Plaid verifies its authenticity by fetching your JWKS from the jwks_uri in your .well-known config.

How verification works:

  1. Fetch JWKS from your jwks_uri (Plaid caches this for speed).
  2. Use the kid in the token header to find the right public key.
  3. Verify the digital signature and claims (issuer, audience, expiration).
  4. Extract the sub field (the user identifier).
Quick recap
User -> /authorize -> authenticates
-> /token -> returns tokens
-> Plaid fetches JWKS -> verifies ID token -> Done

Plaid now has everything needed to access your Core Exchange API on behalf of the user.

Refresh flow

Access tokens expire. That's by design. When Plaid needs fresh data (e.g., balance checks before ACH transfers or transaction updates), it uses the refresh token to get a new access token. Same token_endpoint, different parameters.

Body parameters

Body parameterDescriptionValue
grant_typeSpecifies that Plaid is requesting a new access token to replace the expired access token.Will be set to: refresh_token
refresh_tokenThe refresh token you issued to Plaid. Recommended expiration: 13+ months. This expiration aligns with the reauthorization requirement (12 months) while providing a buffer for high-traffic periods, such as tax season, when users may not complete reauthorization immediately.Example: dhcsrtjsrgayvkdisfdgntshstu...
Plaid request example
curl --request POST 'https://auth.firstplatypusbank.com/oauth2/v1/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header "Authorization: Basic YzVhNTI0NWIwNjJiZjg0MjBkMTFhYjQzNjFiMjhhMTU6clZYWU9vUVM0ckhVRzc5bl80OGFs" \
--data-raw '{
    "grant_type": "refresh_token",
    "refresh_token": "dhcsrtjsrgayvkdisfdgntshstu..."
}'

Validate the grant_type and refresh_token, then respond with new tokens: access_token, id_token, and refresh_token.

Pro tip: Rotate the refresh token each time for better security (new token, new expiration). But if you need to reuse the same refresh token across multiple requests, that works too.

Response parameters

PropertyDescription
access_tokenAn opaque string (likely a JWT structured according to the OAuth2 specification). Plaid will present this string as a bearer token to all requests made to your Core Exchange API. The access_token encodes the user's identity and the scope of access granted.
expires_inThe lifetime of the access token, in seconds. Typically 15 minutes (900 seconds). Plaid checks for expiration before using an access token. If the access token has expired, Plaid will use the refresh token to request a new access token. If your organization expires the token before the stated expiration date, Plaid expects to receive a 401 response with an error code of "602 not authorized".
id_tokenAn OIDC ID token. Plaid only reads the sub field from this token. In a deployment with multiple financial institutions, the sub field must be unique to each financial institution. (It doesn't need to be unique to the user across all financial institutions.)
refresh_tokenAn opaque string (likely a JWT) that can be used to request a new access token. When using refresh token rotation, a new refresh token replaces the one sent in the request. If not using rotation, this may be the same refresh token. Recommended expiration: 13+ months. Set to at least 12 months to comply with reauthorization requirements. The 13+ month range provides buffer time for users to complete reauthorization during peak periods without connections failing.
Example success response
{
  "access_token": "lngarogglkcangasgabba...",
  "expires_in": 900,
  "id_token": "snsyjrhvjdtvyjvsgcegaethstj...",
  "refresh_token": "dhcsrtjsrgayvkdisfdgntshstu..."
}

Quick troubleshooting

SymptomCommon causeFix
User stuck on loading screenAuthorization endpoint timeoutCheck endpoint latency < 3.5s
"Invalid code" errorsCode reuse or expirationExpire codes after 10 min, single-use
Token validation failsJWKS not accessibleVerify jwks_uri is public HTTPS
Refresh token rejectedToken expiredSet expiration to 13+ months
invalid_grant errorsCode/redirect URI mismatchVerify exact match on redirect_uri
CORS errorsMissing headersAdd Access-Control-Allow-Origin

See the full error reference below, along with the Troubleshooting Guide for detailed debugging.

When things go wrong

OAuth errors fall into three categories, each handled differently. (Full details in RFC 6749 section 4.1.)

What if the redirect_uri doesn't match? If the redirect_uri in the authorization request doesn't match the registered value for that client_id, don't redirect. Show an error page instead. For Plaid, the redirect URI should always be https://cdn.plaid.com/link/v2/stable/oauth.html. Redirecting to an unregistered URI is a security risk that could expose authorization codes to attackers.

User cancellation

If the user cancels authentication or another error occurs (excluding redirect URI mismatches), redirect them back to Plaid with these parameters:

Query parameterDescription
errorThe reason for the error. See the Authorization errors table below for a list of possible errors.
stateThe opaque string Plaid passed as the state parameter in the authorization_endpoint redirect step.
Example error response
https://cdn.plaid.com/link/v2/stable/oauth.html?error=access_denied
&state=eyJvYXV0aF9zdGF0ZV

Authorization error codes

Error valueDescription
invalid_requestThe request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed.
unauthorized_clientThe client is not authorized to request an authorization code using this method.
access_deniedThe resource owner or authorization server denied the request.
unsupported_response_typeThe authorization server does not support obtaining an authorization code using this method.
invalid_scopeThe requested scope is invalid, unknown, or malformed.
server_errorThe authorization server encountered an unexpected condition that prevented it from fulfilling the request. (This error code is needed because a 500 Internal Server Error HTTP status code cannot be returned to the client via an HTTP redirect.)
temporarily_unavailableThe authorization server is currently unable to handle the request due to a temporary overload or maintenance of the server. (This error code is needed because a 503 Service Unavailable HTTP status code can't be returned to the client via an HTTP redirect.)

Token exchange and refresh flow errors

Errors during token exchange or refresh? Return HTTP 400 with an error field from the table below. Add error_description and error_uri if you want to help with debugging. (Details in RFC 6749 section 5.2.)

Example error response
{
  "error": "invalid_grant",
  "error_description": "Authorization grant does not match redirect URI",
  "error_uri": "https://www.your-org.com/human-readable-error-info/"
}

Token error codes

Error codeDescription
invalid_requestThe request is missing a required parameter.
invalid_clientClient authentication failed.
invalid_grantThe provided authorization grant or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client (rely on error description from FIs to further break this out).
unauthorized_clientThe authenticated client is not authorized to use this authorization grant type.
unsupported_grant_typeThe authorization grant type is not supported by the authorization server.
invalid_scopeThe requested scope is invalid, unknown, or malformed.

Expired access token

When Plaid makes API requests with an access token, validate that it hasn't exceeded the lifetime you specified in expires_in during token issuance. Track when each token was issued and check that current_time - issue_time < expires_in.

If the access token has expired, send a 401 response with an error code of "602 not authorized". Plaid will automatically use the refresh token to request a new access token.