Credential:  
None
  • None
  • Manage Credentials
Launch IDE ↗

Stitch SSO

5.3. Introduction

Note

This introduction explains how to use Stitch SSO using a public client. Public clients are for applications which do not query the Stitch API using a backend.

Confidential client users are still strongly advised to read the guide below as the confidential client flow is a derivative of the public one.

This section will walk you through being able to sign in with Stitch SSO.

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

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:

SSO Public Client Overview

Getting an access token that can query the API is a 2-step process:

  1. Obtaining an authorization code
  2. Using the authorization code to obtain access and refresh tokens

Obtaining an Authorization Code

The first step in the process entails navigating the browser (or app) to the https://secure.stitch.money/connect/authorize 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.

Authorization Request Query Parameters
ParameterDescription
client_idThis is a unique ID that will be issued to you by a Stitch engineer
scope

A 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 are:

  • accounts
  • transactions
  • balances
  • accountholders
  • client_apicreditusage (only for client credentials usage)

To determine which scopes are required for specific queries, please consult the Stitch API reference

response_typeShould always have a value of "code". Instructs Stitch SSO to return an authorization code
redirect_uriOne of a whitelisted set of URLs. After login, the user is redirected back to this URL. The redirect_uri is whitelisted to prevent open redirect attacks
nonceA nonce is required to mitigate replay attacks. The value of the nonce should be a cryptographically secure random string, and 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 value. 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 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 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 random string, between 43 and 128 characters long, using the characters A-Z, a-z, 0-9, and the punctuation characters -._~ (hyphen, period, underscore, and tilde).

The following JavaScript code can be used to generate the verifier challenge pair in modern browsers:

1function base64UrlEncode(byteArray) {
2 const charCodes = String.fromCharCode(...byteArray);
3 return window.btoa(charCodes)
4 .replace(/\+/g, '-')
5 .replace(/\//g, '_')
6 .replace(/=/g, '');
7}
8
9async function sha256(verifier) {
10 const msgBuffer = new TextEncoder('utf-8').encode(verifier);
11
12 // hash the message
13 const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
14
15 return new Uint8Array(hashBuffer);
16}
17
18async function generateVerifierChallengePair() {
19 const randomBytes = crypto.getRandomValues(new Uint8Array(32));
20
21 const verifier = base64UrlEncode(randomBytes);
22 console.log('Verifier:', verifier);
23
24 const challenge = await sha256(verifier).then(base64UrlEncode);
25 console.log('Challenge:', challenge)
26
27 return [verifier, challenge];
28}
29
30generateVerifierChallengePair();

Generating the State and Nonce

The state and nonce fields are required for making 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 authorization request completes, so is convenient for that purpose, while also mitigating CSRF.

The following JavaScript code generates valid state and nonce values:

1function base64UrlEncode(byteArray) {
2 const charCodes = String.fromCharCode(...byteArray);
3 return window.btoa(charCodes)
4 .replace(/\+/g, '-')
5 .replace(/\//g, '_')
6 .replace(/=/g, '');
7}
8
9function generateRandomStateOrNonce() {
10 const randomBytes = crypto.getRandomValues(new Uint8Array(32));
11 return base64UrlEncode(randomBytes);
12}
13
14const state = generateRandomStateOrNonce();
15console.log('State:', state);
16
17const nonce = generateRandomStateOrNonce();
18console.log('Nonce:', state);

Building the URL

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

1function buildAuthorizationUrl(clientId, challenge, redirectUri, state, nonce, scopes) {
2 const search = {
3 client_id: clientId,
4 code_challenge: challenge,
5 code_challenge_method: 'S256',
6 redirect_uri: redirectUri,
7 scope: scopes.join(' '),
8 response_type: 'code',
9 nonce: nonce,
10 state: state
11 };
12 const searchString = Object.entries(search).map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join('&');
13 return `https://secure.stitch.money/connect/authorize?${searchString}`;
14}

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.

1https://secure.stitch.money/connect/authorize?client_id=test-18fbd892-3b73-43c3-a854-c6f78c681349&scope=openid%20offline_access%20transactions%20accounts&response_type=code&redirect_uri=https%3A%2F%2Flocalhost%3A9000%2Freturn&state=2669382d-d6df-4331-a58b-d5743961578b&nonce=5138fa17-ed63-41e5-98b6-047f07efd940&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:

1https://secure.stitch.money/connect/authorize?client_id=test-18fbd892-3b73-43c3-a854-c6f78c681349&scope=openid offline_access transactions accounts&response_type=code&redirect_uri=https://localhost:9000/return&state=2669382d-d6df-4331-a58b-d5743961578b&nonce=5138fa17-ed63-41e5-98b6-047f07efd940&code_challenge=FVF6VCwYJ_XljGdLXVjxs6g-_QRh4_CDutLOf_oIzPw&code_challenge_method=S256

Decoding the Response

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

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

Authorization Response Fragment Parameters
ParameterDescription
codeThe authorization code needed to obtain an 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)

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 an access token.

Using an Authorization Code to retrieve Access and Refresh Tokens

The next step entails retrieving an API access token from the https://secure.stitch.money/connect/token endpoint.

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:

Token Request Body Parameters
ParameterDescription
grant_typeFor the purposes of retrieving the token, should always be "authorization_code"
client_idThis is a unique ID that will be issued to you by a stitch engineer. 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_verifier generated in the previous step

Note that an authorization code can only be used once, thereafter becoming invalid. This is to mitigate opportunities for replay attacks.

Retrieving the Token Using cURL

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

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

1clientId='test-18fbd892-3b73-43c3-a854-c6f78c681349'
2authorizationCode='DH7-TaofOSCFlsQwZAeEfmap1eXPeH7nmeOMtDJhdOw'
3redirectUri='https%3A%2F%2Flocalhost%3A9000%2Freturn'
4codeVerifier='chy0EHtabciY0kjRzCDr113O2slAa-xY_zvdApkXxvw'
5
6curl -X POST \
7 https://secure.stitch.money/connect/token \
8 -H 'Content-Type: application/x-www-form-urlencoded' \
9 -d "grant_type=authorization_code&client_id=$clientId&code=$authorizationCode&redirect_uri=$redirectUri&code_verifier=$codeVerifier"

Retrieving the Token Using JavaScript and the Fetch API

1async function retrieveTokenUsingAuthorizationCode(clientId, redirectUri, verifier, code) {
2 const body = {
3 grant_type: 'authorization_code',
4 client_id: clientId,
5 code: code,
6 redirect_uri: redirectUri,
7 code_verifier: verifier
8 }
9 const bodyString = Object.entries(body).map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join('&');
10
11 const response = await fetch('https://secure.stitch.money/connect/token', {
12 method: 'post',
13 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
14 body: bodyString,
15 });
16
17 const responseBody = await response.json();
18 console.log('Tokens: ', responseBody);
19 return responseBody;
20}

Retrieving the Token Using Postman

Download the following Postman collection, and import it into Postman:

Getting Started.postman_collection.json

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

Response Body

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

1{
2 "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6Im9TbWt2RmhqVWNia0I4MjRrek5fWkEiLCJ0eXAiOiJKV1QifQ.eyJuYmYiOjE1NzYxMzg4OTgsImV4cCI6MTU3NjEzOTE5OCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAxIiwiYXVkIjoiZjVjYzk0ZWUtZTk1MS00MGQzLWEzMWUtNzk0NjU5ODA2MjMwIiwibm9uY2UiOiJ4eXoiLCJpYXQiOjE1NzYxMzg4OTgsImF0X2hhc2giOiJqcFVVcGh5VWtaQ3diUXNZaG5zNmF3Iiwic19oYXNoIjoidW5nV3Y0OEJ6LXBCUVVEZVhhNGlJdyIsInNpZCI6InM0LWd0WXFNQUFQWkhfcy1tdjBsa3ciLCJzdWIiOiJ1c2VyL2Y1Y2M5NGVlLWU5NTEtNDBkMy1hMzFlLTc5NDY1OTgwNjIzMC9mbmIvOTdjY2M1ZmQtY2I5NS00NjViLWE0ZGMtNzYxMzk2MzNiMDdiIiwiYXV0aF90aW1lIjoxNTc2MTM3MjkxLCJpZHAiOiJsb2NhbCIsImFtciI6WyJwd2QiXX0.nVKdmdOZbNaN1lutLmvaT_nIf1kYKphXNzFHioapDigT3pNwYwmDVGQ54V40kvwJBF8PiQBzfp2hdV1S6ltJqWmnuEtLteg240FimYMdrX1sD3nQKzoMKVkbComY5lnB79QvFc5-dWRCauvZW9LY0mhnXdzRFXhb-Jv21XwzMhE_y21vPAVdPrk0jB7jyk15OZ82RwwlZYhdcBUP7Se4BMvvr4wJJEBWgxAIciFDa6gLqDyL-eInYePjhglV6KrrIHHM9C3PEd1kJJHD6uDkOf3858kS9Nr1Mnj2oNJRvpahWq8VhItsw_5JItfwaTYQoHN25Zk4A0a5Nox6weD-RA",
3 "access_token": "nXPh9P16drRQLnAmwy9Sf072U81KVNNa6iqduWX6kK4",
4 "expires_in": 3599,
5 "token_type": "Bearer",
6 "refresh_token": "Vnzqys6mDyRAOz_ZOb6LOv2keOmyvOLlHEd1s6Hc2Xg",
7 "scope": "openid transactions accounts offline_access"
8}

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

Authorization Code Response Body Parameters
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

Making API calls with the 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:

1accessToken='M6kGvDwSCBUx2oYy-CnphEdO7Ad9TnHL1fWaNaSaDSo'
2
3curl -X POST \
4 https://api.stitch.money/graphql \
5 -H 'Content-Type: application/json' \
6 -H "Authorization: Bearer $accessToken" \
7 -d '{"query":"query ListTransactions {\n user {\n __typename\n ... on UserInteractionRequired {\n userInteractionUrl\n }\n ... on User {\n id\n bankAccounts {\n name\n }\n }\n }\n}\n","variables":null,"operationName":"ListTransactions"}'

Making an Authenticated Query using JavaScript and the Fetch API

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

1function queryStitchApi(accessToken) {
2 return fetch('https://api.stitch.money/graphql', {
3 'credentials': 'include',
4 'headers': {
5 'Content-Type': 'application/json',
6 'Authorization': `Bearer ${accessToken}`
7 },
8 'body': `{\"query\":\"query ListTransactions {\\n user {\\n __typename\\n ... on UserInteractionRequired {\\n userInteractionUrl\\n }\\n ... on User {\\n id\\n bankAccounts {\\n name\\n }\\n }\\n }\\n}\\n\",\"variables\":null,\"operationName\":\"ListTransactions\"}`,
9 'method': 'POST',
10 'mode': 'cors'
11 });
12}

Making an Authenticated query using Postman

Download the following Postman collection, and import it into Postman:

Getting Started.postman_collection.json

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 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 https://secure.stitch.money/connect/token endpoint. The call should have the following parameters:

Refresh Token Request Body Parameters
ParameterDescription
grant_typeFor the purposes of using the refresh token, should always be the value refresh_token
client_idThis is a unique ID that will be issued to you by a Stitch engineer. 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

Note that a refresh token can only be used once, the result of the token call will include a new access and refresh token.

Requesting a new token also creates a new banking session. Depending on the bank, this means that it may trigger a login notification, or bring up a second-factor prompt. The latter case will not interrupt the process of retrieving a new access token, but follow-up requests to the API may require user interaction.

Refreshing the Token Using cURL

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

You'll need to replace the clientId and refreshToken with the appropriate values.

This request if correctly formed, will return a JSON payload with the token

1clientId='test-18fbd892-3b73-43c3-a854-c6f78c681349'
2refreshToken='Vnzqys6mDyRAOz_ZOb6LOv2keOmyvOLlHEd1s6Hc2Xg'
3
4curl -X POST \
5 https://secure.stitch.money/connect/token \
6 -H 'Content-Type: application/x-www-form-urlencoded' \
7 -d "grant_type=refresh_token&client_id=$clientId&refresh_token=$refreshToken"

Refreshing the Token Using JavaScript and the Fetch API

1async function retrieveTokenUsingRefreshToken(clientId, refreshToken) {
2 const body = {
3 grant_type: 'refresh_token',
4 client_id: clientId,
5 refresh_token: refreshToken
6 }
7 const bodyString = Object.entries(body).map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join('&');
8
9 const response = await fetch(tokenEndpoint, {
10 method: 'post',
11 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
12 body: bodyString,
13 });
14
15 const responseBody = await response.json();
16 console.log('Tokens: ', responseBody);
17 return responseBody;
18}

Refreshing the Token Using Postman

Download the following Postman collection, and import it into Postman:

Getting Started.postman_collection.json

The second request in the collection is "Refresh Token". Replace the entries in the Body tab with the appropriate values and click send. The request if correctly formed will return a JSON payload with the token.

Response Body

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

1{
2 "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6Im9TbWt2RmhqVWNia0I4MjRrek5fWkEiLCJ0eXAiOiJKV1QifQ.eyJuYmYiOjE1NzYyMjM1MjEsImV4cCI6MTU3NjIyMzgyMSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAxIiwiYXVkIjoiZjVjYzk0ZWUtZTk1MS00MGQzLWEzMWUtNzk0NjU5ODA2MjMwIiwiaWF0IjoxNTc2MjIzNTIxLCJhdF9oYXNoIjoiZmNaeVN5MnBSYlBKMnUwSW1OWUNlQSIsInN1YiI6InVzZXIvZjVjYzk0ZWUtZTk1MS00MGQzLWEzMWUtNzk0NjU5ODA2MjMwL2ZuYi9kY2QxMzczNy0xMTc4LTRhMTUtOGQwYy00MGE0YzBiMmU1ODkiLCJhdXRoX3RpbWUiOjE1NzYyMjM0MTAsImlkcCI6ImxvY2FsIiwiYW1yIjpbInB3ZCJdfQ.ISRH-W9SgDJmTAcAFcrRggDb4Ym-0xmZ-Lbv1gfceUr00wjV8eiZcf_EE1Ca9t7fXgeFMsWTclc-ZxX5szWQAvLaqGdFo-3IlKuPgmftTmfTAb1y7_RWNIjuTjDtJvWzLnf1WGO62Ki_uz2kB3VicneDEzSx5YJRGJz6tZ5LBvrCAj_WQ4mpodNEawuC1komJMhROVtLdM7tAAE7BPhF3Ks0v-SiAzh8QtxBAs8l-13dcmgeXrgCiND5i520QtuOhdVV7DNQ1BvP8SGxMhmuA4V5s3V2BDTIUkR8_IsMq6OE-BxjXfflM1QGlV7_tRs52w5CLxfuFNX_sVIHNIWQzw",
3 "access_token": "ey-K-qPC5Cs0ERd0eDuLJI636rLEnd3Kwi5L1NrVNJY",
4 "expires_in": 3599,
5 "token_type": "Bearer",
6 "refresh_token": "Wce1_ujqCD8B-BHAzrV-1S_3WFsHSxXKWUHGtfJvZvc",
7 "scope": "openid accounts transactions offline_access"
8}
Refresh Token Response Body Parameters
ParameterDescription
id_tokenSame as the one returned from the authorize call. id_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 next refresh_token which needs to be stored for later use
scopeThe scopes that were granted by the user

Token Expiry

Access tokens are configured to have a 1-hour lifetime by default. The token expiry is indicated by the expires_in field (displayed in seconds) which is returned when successfully retrieving a token. You can refresh your token before the indicated time to ensure you always have a fresh token

Queries with an expired token will yield the following response:

1{
2 "errors": [
3 {
4 "message": "UNAUTHENTICATED: Token is expired or malformed"
5 }
6 ],
7 "extensions": {
8 "code": "UNAUTHENTICATED"
9 }
10}