Skip to main content

What are User Tokens?

Most of the functionality of the Stitch API is protected by user tokens. A user token represent a client's authorization to access certain features of the user's bank account.

While we strongly recommend going through this section and familiarising yourself with Stitch SafeLink, there are many libraries which implement OpenID Connect Standards mentioned in the FrontEnd Integration section and will perform much of the functionality required.

Before continuing, it may be useful to consider the following diagram. The diagram serves as a map of the terrain we need to navigate; namely the process required to acquire a token to interact with the API outside the IDE:

sequenceDiagram participant Client application participant participant rect rgba(236, 236, 255, .5) Note right of Client application: OpenID PCKE hybrid flow Client application->> redirect users to /connect/authorize activate>> user logs into bank account and grants consent>>Client application: redirect to supplied redirect_uri with query string containing authorization code deactivate Client application->> POST to /connect/token with code, client_id, code_verifier and redirect_uri activate>>Client application: returns user_token and if offline-access granted, refresh_token deactivate end Client application->> POST query/mutation to /graphql with Authorization header containing Bearer $token activate>>Client application: query result deactivate opt if token expired and has refresh_token Client application->> POST to /connect/token with refresh_token and client_id activate>>Client application: returna new user_token and refresh_token deactivate end

Acquiring a user token that can query the API involves a 2-step process:

  1. Obtain an authorization code
  2. Use the authorization code to obtain user access and refresh tokens

Obtaining an Authorization Code

The first step in the process entails navigating the browser (or app) to the endpoint. If the query string parameters passed to the endpoint are correctly formed, the user is redirected to the login and consent UI.

The table below lists the required parameters. Note that all values should be URL encoded. To complete the rest of the tutorial, you'll need at least the openid, accounts, balances, and offline_access scopes.

client_idThis is the unique ID of the client you generated.
scopeYou’ll need a non-empty, space-separated list of requested scopes. The openid scope is required. If you want to use a refresh token, request the offline_access scope. Stitch API-specific scopes include: accounts, transactions, balances, accountholders. To determine which scopes are required for specific queries, please consult the Stitch API reference.
response_typeInstructs Stitch to return an authorization code. This should always have a value of "code".
redirect_uriOne of a whitelisted set of URLs. After login, the user should be redirected back to this URL. The redirect_uri is whitelisted to prevent open redirect attacks. This should always be protected by SSL and HSTS.
nonceA nonce is required to mitigate replay attacks. The value of the nonce should be a cryptographically secure, random string between 32 and 300 characters in length. The nonce is later included in the id_token found in the token endpoint response.
stateThe state parameter is required to prevent CSRF (Cross Site Request Forgery). Like the nonce, this should be a cryptographically secure, random string between 32 and 300 characters in length. The value of the state parameter should be stored in the application (e.g. in local storage, or as a cookie). When the authorization request returns, the state is included, and should be validated against the stored value to properly protect against CSRF.
code_challengeA base64URL encoding of the SHA256 hashed code_verifier created below.
code_challenge_methodThe hash algorithm used for the code_challenge. In this case the value will be "S256" (case-sensitive).

Generating a Code Verifier

In order for the server to correlate the authorization and user access code requests, a unique code_verifier needs to be generated for each request. The hashed value of this code (the code_challenge) is sent with the authorization code request, and the unhashed value (the code_verifier) is sent with the user access token request.

This code_verifier, code_challenge pair is needed to confirm to the SSO server that the token request is coming from the same party as the authorization request, preventing several classes of vulnerabilities.

The code_verifier is a cryptographically secure, random string between 43 and 128 characters long. The code_verifier consists of the characters A-Z, a-z, 0-9, and the punctuation characters -._~ (hyphen, period, underscore, and tilde).

The following code snippets show how to generate the code_verifier and code_challenge pairs across a few common languages:

async function sha256(verifier) {
const msgBuffer = new TextEncoder("utf-8").encode(verifier);

// hash the message
const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer);

return new Uint8Array(hashBuffer);

async function generateVerifierChallengePair() {
const randomBytes = crypto.getRandomValues(new Uint8Array(32));

const verifier = base64UrlEncode(randomBytes);
console.log("Verifier:", verifier);

const challenge = await sha256(verifier).then(base64UrlEncode);
console.log("Challenge:", challenge);

return [verifier, challenge];


To verify your code verifier and code challenge pair, paste them into the inputs below.
A unique pair needs to be generated for each request.

Generating the State and Nonce

The state and nonce fields are required to make an authorization request. The state is required to prevent Cross-Site Request Forgery (CSRF), while the nonce is required to prevent replay attacks. In the Stitch IDE, we use the state parameter as a key in localStorage to store the code_verifier and nonce. The state is returned when the authorization request is complete and helps mitigate CSRF.

The following JavaScript code generates valid state and nonce values:

function base64UrlEncode(byteArray) {
const charCodes = String.fromCharCode(...byteArray);
return window
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=/g, "");

function generateRandomStateOrNonce() {
const randomBytes = crypto.getRandomValues(new Uint8Array(32));
return base64UrlEncode(randomBytes);

const state = generateRandomStateOrNonce();
console.log("State:", state);

const nonce = generateRandomStateOrNonce();
console.log("Nonce:", nonce);

It is recommended that the state value is a cryptographically secure, random string that does not correspond to any data used to identify users internally.

If, for any reason, the state field needs to be used as means of identification, we recommend creating a stitch_authorization_requests table on your backend to house relevant details about the authorization requests.

The entries contained should be easily mapped to any internal identification. Suggested columns for this table are as follows:

stitch_stateThe same state value sent with the endpoint. This column is mandatory because it allows the entry to be linked with an authorization request
user_idAn internally determined value used to identify the users within your system. We recommend that this is a cryptographically secure value
code_verifierThe code_verifier generated in previous steps. The code_verifier is the unhashed code_challenge
code_challengeThe code_challenge generated in previous steps

These columns are suggestions and only the stitch_state is mandatory. The user_id could be replaced with any internal identifier and can be used in conjunction with other columns that are relevant to map.

During the authorization flow, the state value will always be the original state value as generated and supplied and can be used to identify the authorization request. Once a response is received from the authorization flow, the contained state value can be used to map the authorization request to an entry in the stitch_authorization_requests table.

Building the Authorization URL

Once you've created a verifier challenge pair, the state, and the nonce, you can create an authorization request URL. The following JavaScript code helps you build your URL:

function buildAuthorizationUrl(
) {
const search = {
client_id: clientId,
code_challenge: challenge,
code_challenge_method: "S256",
redirect_uri: redirectUri,
scope: scopes.join(" "),
response_type: "code",
nonce: nonce,
state: state,
const searchString = Object.entries(search)
.map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
return `${searchString}`;

Here is an example of a plausible connect URL. Note that the client_id and redirect_uri values would need to be replaced with your own for this example to work.

Some browsers may mangle the URL if you paste it into the address bar. If that is the case, it may be helpful to paste in this URL which is the same as the above, just without the URL encoding: offline_access transactions accounts&response_type=code&redirect_uri=https://localhost:9000/return&state=J_Ug3dCQJG61Tt2Sejt3TY5ExgqemTXpEFhyFGapXwA&nonce=fim8Mwz0yHE85NOfgMD_sk3MdKzq2vQ_QnC0KUxIGT4&code_challenge=FVF6VCwYJ_XljGdLXVjxs6g-_QRh4_CDutLOf_oIzPw&code_challenge_method=S256

Decoding the Response

If the connect URL is correctly formed, once the user has signed in and been granted access, they'll be redirected back to theredirect_uri.

If all went well, the value of the query string (the part of a URL delimited by a ?) will contain the following URL encoded parameters:

codeThe authorization code needed to obtain a user access token
scopeA space delimited list of scopes that were granted by the user
stateThe string that was passed in as the state parameter to the authorization request
session_stateAn opaque string that represents the End-User's login state. It can be used in advanced scenarios to verify whether a user session is still active (see

You'll need to grab the code from the query string to continue onto the next step, along with the redirect_uri supplied to the connect call. Users may choose to cancel while signing in, in which case they will be redirected without an authorization code in the query string. This case should be handled before attempting to retrieve a user access token.


In the event that the authorization request fails (eg. a user chooses to quit the session), then the response will include only the state parameter above.

Using an Authorization Code to Retrieve User Access and Refresh Tokens

The next step entails retrieving an API user access token from the endpoint.

In order to do so, a client_secret needs to be used. Follow the client secret guide to learn how to use a client secret.

To retrieve the token, make a POST request to the endpoint, with a content type of application/x-www-form-urlencoded and the following fields in the body:

grant_typeFor the purposes of retrieving the token, should always be "authorization_code"
client_idThis is the unique ID of the client you generated. The same as the client_id used in the previous step
codeThe code retrieved from the previous step
redirect_uriThe redirect_uri used in the previous step
code_verifierThe unhashed code_challenge generated in the previous step
client_secretThe value of your client_secret

Note that an authorization code can only be used once. Thereafter, it becomes invalid. The reason for this is to mitigate opportunities for replay attacks.

Retrieving the Token Using cURL

This example bash script uses cURL to retrieve the user access and refresh token.

You'll need to replace the clientId, clientSecret, authorizationCode, codeVerifier and redirectUri with the appropriate values. If correctly formed, this request will return a JSON payload with the token.

clientSecret='<your client secret>'

curl -X POST \ \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d "grant_type=authorization_code&client_id=$clientId&code=$authorizationCode&redirect_uri=$redirectUri&code_verifier=$codeVerifier&client_secret=$clientSecret"

Retrieving the Token Using JavaScript and the Fetch API

The example Javascript function below uses fetch to retrieve the authorization code. You'll need to pass in appropriate values for clientId, redirectUri, verifier, code, and clientSecret to the function retrieveTokenUsingAuthorizationCode

async function retrieveTokenUsingAuthorizationCode(
) {
const body = {
grant_type: "authorization_code",
client_id: clientId,
code: code,
redirect_uri: redirectUri,
code_verifier: verifier,
client_secret: clientSecret,
const bodyString = Object.entries(body)
.map(([k, v]) => `${k}=${encodeURIComponent(v)}`)

const response = await fetch("", {
method: "post",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: bodyString,

const responseBody = await response.json();
console.log("Tokens: ", responseBody);
return responseBody;

Retrieving the Token Using Postman

To get started, first import this Postman collection into your local Postman collections.

The first request in the collection is "User Token". Replace the entries in the Body tab with the appropriate values, and click send. If correctly formed, the request will return a JSON payload with the token.

A typical response body returned from the token request endpoint will look like the following:

"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6Im9TbWt2RmhqVWNia0I4MjRrek5fWkEiLCJ0eXAiOiJKV1QifQ.eyJuYmYiOjE1NzYxMzg4OTgsImV4cCI6MTU3NjEzOTE5OCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAxIiwiYXVkIjoiZjVjYzk0ZWUtZTk1MS00MGQzLWEzMWUtNzk0NjU5ODA2MjMwIiwibm9uY2UiOiJ4eXoiLCJpYXQiOjE1NzYxMzg4OTgsImF0X2hhc2giOiJqcFVVcGh5VWtaQ3diUXNZaG5zNmF3Iiwic19oYXNoIjoidW5nV3Y0OEJ6LXBCUVVEZVhhNGlJdyIsInNpZCI6InM0LWd0WXFNQUFQWkhfcy1tdjBsa3ciLCJzdWIiOiJ1c2VyL2Y1Y2M5NGVlLWU5NTEtNDBkMy1hMzFlLTc5NDY1OTgwNjIzMC9mbmIvOTdjY2M1ZmQtY2I5NS00NjViLWE0ZGMtNzYxMzk2MzNiMDdiIiwiYXV0aF90aW1lIjoxNTc2MTM3MjkxLCJpZHAiOiJsb2NhbCIsImFtciI6WyJwd2QiXX0.nVKdmdOZbNaN1lutLmvaT_nIf1kYKphXNzFHioapDigT3pNwYwmDVGQ54V40kvwJBF8PiQBzfp2hdV1S6ltJqWmnuEtLteg240FimYMdrX1sD3nQKzoMKVkbComY5lnB79QvFc5-dWRCauvZW9LY0mhnXdzRFXhb-Jv21XwzMhE_y21vPAVdPrk0jB7jyk15OZ82RwwlZYhdcBUP7Se4BMvvr4wJJEBWgxAIciFDa6gLqDyL-eInYePjhglV6KrrIHHM9C3PEd1kJJHD6uDkOf3858kS9Nr1Mnj2oNJRvpahWq8VhItsw_5JItfwaTYQoHN25Zk4A0a5Nox6weD-RA",
"access_token": "nXPh9P16drRQLnAmwy9Sf072U81KVNNa6iqduWX6kK4",
"expires_in": 900,
"token_type": "Bearer",
"refresh_token": "Vnzqys6mDyRAOz_ZOb6LOv2keOmyvOLlHEd1s6Hc2Xg",
"scope": "openid transactions accounts offline_access"

Note that if the user did not grant permission to have the offline_access scope, the refresh_token will be omitted from the response.

id_tokenid_token contains user profile information and is JWT encoded
access_tokenThe token needed to query the Stitch API
expires_inThe number of seconds until the token expires
refresh_tokenThe refresh_token which needs to be stored for use in later steps
scopeThe scopes that were granted by the user

Making API Calls with the User Access Token

This section serves to show you how to make an authenticated GraphQL query against the Stitch API using cURL, JavaScript and Postman. Integrating Stitch API goes into more detail about how the GraphQL protocol works over HTTP.

Making an Authenticated Query using cURL

Replace the accessToken variable in the script below with the one issued in the previous step:


curl -X POST \ \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $accessToken" \
-d '{"query":"query ListBankAccounts {\n user {\n __typename\n ... on User {\n id\n bankAccounts {\n name\n }\n }\n }\n}\n","variables":null,"operationName":"ListBankAccounts"}'

Making an Authenticated Query using JavaScript and the Fetch API

The function below can be used to make queries to the StitchApi:

function queryStitchApi(accessToken) {
var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("Authorization", `Bearer ${accessToken}`);

var graphql = JSON.stringify({
"query ListBankAccounts { user { bankAccounts { user { bankAccounts { name }}}}}",
variables: {},

var requestOptions = {
credentials: "include",
method: "POST",
headers: myHeaders,
body: graphql,
mode: "cors",

return fetch("", requestOptions);

Making an Authenticated Query using Postman

To get started, first import this Postman collection into your local Postman collections.

Replace the value of the Authorization header in the headers section of Postman with Bearer {{YOUR_TOKEN}}. This should allow you to make queries against the API.

Using the Refresh Token to Create a New Session

Eventually, the user access token you've been using will expire, and API calls will start returning a status code of 401. If that is the case, it means it's time to make use of the refresh token to generate a new token (and a new refresh token as well). This process also uses the endpoint. The call should have the following parameters:

grant_typeFor the purposes of using the refresh token, this should always be the value refresh_token
client_idThis is the unique ID of the client you generated. This is the same as the client_id used in the previous step
refresh_tokenThe refresh_token that was obtained from the authorization code response
client_secretThe value of your client_secret