Skip to main content

User AuthenticationDeprecated

caution

This section includes information around Stitch products and features that are now deprecated. Deprecated aspects of Stitch's plaform are no longer being supported or improved upon.

If you are currently using a deprecated Stitch product, please consider upgrading to one of our newer and equivalent offerings.

To initiate payment using the userInitiatePayment mutation, a user token is required. To acquire a user token, two steps are involved:

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

Obtaining an Authorization Code

The first step in the process entails passing a few query string parameters to the endpoint https://secure.stitch.money/connect/authorize. 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. For LinkPay, you'll at least need the openid, transactions,

accounts, accountholders , balances scopes. Should your application also require refresh_tokens, you can add the offline_access scope to the list of requested scopes.

ParameterDescription
client_idThe unique ID of the client you generated.
scopeA non-empty, space-separated list of requested scopes. LinkPay requires these scopes openid transactions accounts balances accountholders offline_access
response_typeInstructs Stitch to return an authorization code. This should always have a value of code.
redirect_uriOne of the whitelisted URLs in the client configuration. After login, the user will be redirected back to this URL. The redirect_uri is whitelisted to prevent open redirect attacks.
nonceMitigates replay attacks. The value should be a cryptographically secure, random string between 32 and 300 characters in length.
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. Valid characters for the code_verifier are 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];
}

generateVerifierChallengePair();

Code Verifier and Challenge Validation

tip

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 parameters are required to make an authorization request. The state parameter is required to prevent Cross-Site Request Forgery (CSRF), while the nonce parameter is required to prevent replay attacks.

CSRF Protection

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.

The following JavaScript code generates valid state and nonce values:

function base64UrlEncode(byteArray) {
const charCodes = String.fromCharCode(...byteArray);
return window
.btoa(charCodes)
.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:

ColumnDescription
stitch_stateThe same state value sent with the https://secure.stitch.money/connect/authorize 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 unhashed code_challenge generated in previous step.
code_challengeThe hashed code_challenge generated in previous step.

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 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(
clientId,
challenge,
redirectUri,
state,
nonce,
scopes,
) {
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)}`)
.join("&");
return `https://secure.stitch.money/connect/authorize?${searchString}`;
}

Example URL

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.

https://secure.stitch.money/connect/authorize?client_id=test-18fbd892-3b73-43c3-a854-c6f78c681349&scope=openid%20transactions%20accounts%20balances%20accountholders%20offline_access%20paymentinitiationrequest&response_type=code&redirect_uri=https%3A%2F%2Flocalhost%3A9000%2Freturn&state=J_Ug3dCQJG61Tt2Sejt3TY5ExgqemTXpEFhyFGapXwA&nonce=fim8Mwz0yHE85NOfgMD_sk3MdKzq2vQ_QnC0KUxIGT4&code_challenge=FVF6VCwYJ_XljGdLXVjxs6g-_QRh4_CDutLOf_oIzPw&code_challenge_method=S256

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:

https://secure.stitch.money/connect/authorize?client_id=test-18fbd892-3b73-43c3-a854-c6f78c681349&scope=openid transactions accounts balances accountholders offline_access&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 authorization request URL is correctly formed, once the user has signed in and grants access, they'll be redirected back to the redirect_uri. The value of the query string (the part of a URL delimited by a ?) will contain the following URL encoded parameters:

ParameterDescription
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 https://openid.net/specs/openid-connect-session-1_0.html)
caution

Please note that the authorization code expires after 5 minutes. Once it's expired, you'll need to fetch a new one

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.

authorization request failure

In the event that the authorization request fails (e.g. a user chooses to cancel signing in), then the response will include only the state parameter above.

Retrieving a User Token

The next step entails retrieving a user token from the https://secure.stitch.money/connect/token 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 https://secure.stitch.money/connect/token endpoint, with the content type application/x-www-form-urlencoded and the following fields in the body:

ParameterDescription
grant_typeFor the purposes of retrieving a user token, should always be authorization_code
client_idThis is the unique ID of the client you generated
client_secretThe value of your client_secret
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
authorization codes are single use

An authorization code can only be used once. Thereafter, it becomes invalid to mitigate opportunities for replay attacks.

Retrieving the Token Using Postman

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

The request to edit in the collection is Retrieve 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.

Retrieving the Token Using JavaScript

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(
clientId,
redirectUri,
verifier,
code,
clientSecret,
) {
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)}`)
.join("&");

const response = await fetch("https://secure.stitch.money/connect/token", {
method: "post",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: bodyString,
});

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

Response Body

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

{
"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 balances accountholders offline_access"
}
Refresh Tokens and Scopes

If the offline_access scope was not part of the requested scopes in the authorization request URL, the refresh_token will be omitted from above the response.

ParameterDescription
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

Using Refresh Tokens

Eventually, the user token you've been using will expire, and API calls will start returning a status code of 401. If that is the case, and you had added the offline_access scope during linking, you obtained a refresh token which can generate a new user token (and a new refresh token as well).

This process also uses the https://secure.stitch.money/connect/token endpoint and the content type application/x-www-form-urlencoded. The call should have the following parameters:

ParameterDescription
grant_typeFor the purposes of retrieving a 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

Retrieving the Token Using JavaScript

The example Javascript function below uses fetch to get a new user and refresh token pair. You'll need to pass in appropriate values for clientId, refreshToken, and clientSecret to the function retrieveTokenUsingAuthorizationCode

async function refreshUserToken(clientId, refreshToken, clientSecret) {
const body = {
grant_type: "refresh_token",
refresh_token: refreshToken,
client_id: clientId,
client_secret: clientSecret,
};
const bodyString = Object.entries(body)
.map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
.join("&");

const response = await fetch("https://secure.stitch.money/connect/token", {
method: "post",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: bodyString,
});

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