Paid Feature
This is a paid feature.
You can click on the Enable Paid Features button on our dashboard, and follow the steps from there on. Once enabled, this feature is free on the provided development environment.
Multiple Frontend Domains with a Common Backend
You can use the following guide if you have multiple frontend applications
that use the same backend service
.
Using the actual OAuth 2.0 terminology, we can say that each frontend
can be considered a Client and the backend
will act as both an Authorization Server and a Resource Server.
info
If your frontend applications are on the same domain, but on different sub-domains, you can use Session Sharing Across Subdomains
The authentication flow will work in the following way:
- The User accesses the
frontend
app- The application
frontend
redirects the user to Authorization Service authorize URL. - The Authorization Service backend redirects the user to the login UI
- The application
- The User completes the login attempt
- The Authorization Service backend redirects the user to the
callback URL
.
- The Authorization Service backend redirects the user to the
- The User accesses the callback URL
- The
frontend
uses the callback URL information to obtain a OAuth2 Access Token from the Authorization Service.
- The
info
This guide assumes that you already have setup and configured SuperTokens in your Authorization Service.
For more information on how to do that please check our quickstart guide.
#
1. Enable the OAuth2 features from the DashboardYou will first have to enable the OAuth2 features from the SuperTokens.com Dashboard.
- Open the SuperTokens.com Dashboard
- Click on the Enabled Paid Features button
- Click on Managed Service
- Check the OAuth 2.0 option
- Click Save
Now you should be able to use the OAuth2 recipes in your applications.
#
2. Create the OAuth2 Clients- Single app setup
- Multi app setup
For each of your frontend
applications you will have to create a separate OAuth2 client.
This can be done by directly calling the SuperTokens Core API.
# You will have to run this for each one of your applications
# Adjust the clientName and redirectUri based on that
curl -X POST /recipe/oauth/clients \
-H "Content-Type: application/json" \
-H "api-key: " \
-d '{
"clientName": "<YOUR_CLIENT_NAME>",
"responseTypes": ["code"],
"grantTypes": ["authorization_code", "refresh_token"],
"tokenEndpointAuthMethod": "none",
"scope": "offline_access <custom_scope_1> <custom_scope_2>",
"redirectUri": ["https://<YOUR_APPLICATION_DOMAIN>/oauth/callback"],
}'
clientName
- A human-readable name of the client that will be used for identification.responseTypes
- Specifies the types of responses your client expects from the Authorization Server.code
: Indicates that the Client will receive an Authorization Code that will be exchanged for an OAuth2 Access Token.
grantTypes
- The grant types that the Client will use.authorization_code
: Allows exchanging the Authorization Code for an OAuth2 Access Token.refresh_token
: Allows exchanging the OAuth2 Refresh Token for a new OAuth2 Access Token.
tokenEndpointAuthMethod
- Indicates that the process of obtaining an OAuth2 Access Token will not make use of the client secretredirectUri
- A list of URIs to which the Authorization Server will send the user-agent (browser) after completing the authorization step. These can be deep links to mobile or desktop apps as well, but they must be exact URLs, without wildcards.scope
- A space separated string of scopes that the Client will request access to.offline_access
: You need to include this scope if you want to use the OAuth2 Refresh Token to get a new OAuth2 Access Token.
If the creation was successful, the API will return a response that looks like this:
{
"clientName": "<YOUR_CLIENT_NAME>",
"clientId": "<CLIENT_ID>",
"callbackUrls": ["https://<YOUR_APPLICATION_DOMAIN>/oauth/callback"],
}
caution
You will have to save this response because we do not persist it internally for security reasons. In the next steps we will use the values to complete several configurations.
#
Change the default token lifespanBy default, the tokens used in the authorization flow will have the following lifespans:
- OAuth2 Access Token: 1 hour
- OAuth2 ID Token: 1 hour
- OAuth2 Refresh Token: 30 days
If you want to change the default values you need to specify additional properties in the Client creation request body.
Use string values that signify time duration in milliecoseconds, seconds, minutes or hours (e.g. "2000ms"
, "60s"
, "30m"
, "1h"
).
There are no limits on the duration of each token.
- OAuth2 Access Token - Set the
authorizationCodeGrantAccessTokenLifespan
property. - OAuth2 ID Token - Set the
authorizationCodeGrantIdTokenLifespan
property. - OAuth2 Refresh Token - Set both the
authorizationCodeGrantRefreshTokenLifespan
and therefreshTokenGrantRefreshTokenLifespan
properties to the same value.
#
Disable Refresh Token RotationBy default, the OAuth2 Refresh Token wil expire after 30 days. If your use case cannot accomodate the process of changing the OAuth2 Refresh Token for a new one, you can make it so that this behavior does not apply for your implementation.
In order to achieve this behavior just set the enableRefreshTokenRotation
property to false
in the Client creation request body.
#
3. Set Up your Authorization Service Backend#
Initialize the OAuth2 RecipesIn your Authorization Service you will need to initialize the OAuth2Provider recipe.
- NodeJS
- GoLang
- Python
Important
Update the supertokens.init
call to include the new recipe.
import supertokens from "supertokens-node";
import OAuth2Provider from "supertokens-node/recipe/oauth2provider";
supertokens.init({
supertokens: {
connectionURI: "...",
apiKey: "...",
},
appInfo: {
appName: "...",
apiDomain: "...",
websiteDomain: "...",
},
recipeList: [
OAuth2Provider.init(),
]
});
caution
At the moment we do not have support creating OAuth2 providers in the Go SDK.
caution
At the moment we do not have support creating OAuth2 providers in the Python SDK.
#
Update the CORS configurationYou need to setup the Backend API so that it allows requests from all the frontend domains.
- NodeJS
- GoLang
- Python
Important
import express from "express";
import cors from "cors";
import supertokens from "supertokens-node";
import { middleware } from "supertokens-node/framework/express";
const app = express();
// Add your actual frontend domains here
const allowedOrigins = ["<YOUR_WEBSITE_DOMAIN>", "<CLIENT_DOMAIN_1>", "<CLIENT_DOMAIN_2>"];
app.use(cors({
origin: allowedOrigins,
allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()],
credentials: true,
}));
caution
At the moment we do not have support creating OAuth2 providers in the Go SDK.
caution
At the moment we do not have support creating OAuth2 providers in the Python SDK.
#
Implement a Custom Session Verification FunctionGiven that the backend that represents the Authorization Server will also act as a Resource Server we will have to account for this in the way we verify the sessions.
This is needed because we are using two types of tokens:
- SuperTokens Session Access Token: Used during the login/logout flows.
- OAuth2 Access Token: Used to access protected resources and perform actions that need authorization.
Hence we need a way to distinguish between these two and prevent errors.
- NodeJS
- GoLang
- Python
Important
Here is an example of how to implement this in the context of an Express API:
import supertokens from "supertokens-node";
import Session from "supertokens-node/recipe/session";
import express, { Request, Response, NextFunction } from 'express';
import jose from "jose";
interface RequestWithUserId extends Request {
userId?: string;
}
async function verifySession(req: RequestWithUserId, res: Response, next: NextFunction) {
let session = undefined;
try {
session = await Session.getSession(req, res, { sessionRequired: false });
} catch (err) {
if (
!Session.Error.isErrorFromSuperTokens(err) ||
err.type !== Session.Error.TRY_REFRESH_TOKEN
) {
return next(err);
}
}
// In this case we are dealing with a SuperTokens Session
if (session !== undefined) {
const userId = session.getUserId();
req.userId = userId;
return next();
}
// The OAuth2 Access Token needs to be manually extracted and validated
let jwt: string | undefined = undefined;
if (req.headers["authorization"]) {
jwt = req.headers["authorization"].split("Bearer ")[1];
}
if (jwt === undefined) {
return next(new Error("No JWT found in the request"));
}
try {
const tokenPayload = await validateToken(jwt, '<CUSTOM_SCOPE>');
const userId = tokenPayload.sub;
req.userId = userId;
return next();
} catch (err) {
return next(err);
}
}
const JWKS = jose.createRemoteJWKSet(
new URL("<YOUR_API_DOMAIN>/authjwt/jwks.json"),
);
// This is a basic example on how to validate an OAuth2 Token
// We have a separate page that talks more in depth about the process
async function validateToken(jwt: string, requiredScope: string) {
const { payload } = await jose.jwtVerify(jwt, JWKS, {
requiredClaims: ["stt", "scp", "sub"],
});
if (payload.stt !== 1) throw new Error("Invalid token");
const scopes = payload.scp as string[];
if (!scopes.includes(requiredScope)) throw new Error("Invalid token");
return payload;
}
// You can then use the function as a middleware for a protected route
const app = express();
app.get("/protected", verifySession, async (req, res) => {
// Custom logic
});
For more information on how to verify the OAuth2 Access Tokens please check our separate guide.
caution
At the moment we do not have support creating OAuth2 providers in the Go SDK.
caution
At the moment we do not have support creating OAuth2 providers in the Python SDK.
#
4. Configure the Authorization Service FrontendInitialize the OAuth2Provider
recipe on the frontend of your Authorization Service.
info
If you want to use your own custom UI check our separate guide that explains all the steps that you have to take into account.
#
Initialize the Recipe- React
- Angular
- Vue
Add the import statement for the new recipe and update the list of recipe to also include the new initialization.
import OAuth2Provider from "supertokens-auth-react/recipe/oauth2provider";
import SuperTokens from "supertokens-auth-react";
SuperTokens.init({
appInfo: {
appName: "...",
apiDomain: "...",
websiteDomain: "...",
},
recipeList: [
OAuth2Provider.init()
]
});
#
Include the pre built UI in the rendering tree.Update the AuthComponent
so that it also includes the OAuth2Provider
recipe.
You will have to add a new item in the recipeList
array.
import {init as (window as any).supertokensUIInit} from "supertokens-auth-react";
import (window as any).supertokensUIOAuth2Provider from "supertokens-auth-react/recipe/oauth2provider";
import { Component, OnDestroy, AfterViewInit, Renderer2, Inject } from "@angular/core";
import { DOCUMENT } from "@angular/common";
@Component({
selector: "app-auth",
template: '<div id="supertokensui"></div>',
})
export class AuthComponent implements OnDestroy, AfterViewInit {
constructor(
private renderer: Renderer2,
@Inject(DOCUMENT) private document: Document
) { }
ngAfterViewInit() {
this.loadScript('^{jsdeliver_prebuiltui}');
}
ngOnDestroy() {
// Remove the script when the component is destroyed
const script = this.document.getElementById('supertokens-script');
if (script) {
script.remove();
}
}
private loadScript(src: string) {
const script = this.renderer.createElement('script');
script.type = 'text/javascript';
script.src = src;
script.id = 'supertokens-script';
script.onload = () => {
(window as any).supertokensUIInit("supertokensui", {
appInfo: {
appName: "<YOUR_APP_NAME>",
apiDomain: "<YOUR_API_DOMAIN>",
websiteDomain: "<YOUR_WEBSITE_DOMAIN>",
apiBasePath: "/auth",
websiteBasePath: "/auth"
},
recipeList: [
// Don't forget to also include the other recipes that you are already using
(window as any).supertokensUIOAuth2Provider.init()
],
});
}
this.renderer.appendChild(this.document.body, script);
}
}
Update the AuthView
component so that it also includes the OAuth2Provider
recipe.
You will have to add a new item in the recipeList
array, inside the (window as any).supertokensUIInit
call.
import {init as (window as any).supertokensUIInit} from "supertokens-auth-react";
import (window as any).supertokensUIOAuth2Provider from "supertokens-auth-react/recipe/oauth2provider";
<script lang="ts">
import { defineComponent, onMounted, onUnmounted } from 'vue';
export default defineComponent({
setup() {
const loadScript = (src: string) => {
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = src;
script.id = 'supertokens-script';
script.onload = () => {
(window as any).supertokensUIInit("supertokensui", {
appInfo: {
appName: "<YOUR_APP_NAME>",
apiDomain: "<YOUR_API_DOMAIN>",
websiteDomain: "<YOUR_WEBSITE_DOMAIN>",
apiBasePath: "/auth",
websiteBasePath: "/auth"
},
recipeList: [
// Don't forget to also include the other recipes that you are already using
(window as any).supertokensUIOAuth2Provider.init()
],
});
};
document.body.appendChild(script);
};
onMounted(() => {
loadScript('^{jsdeliver_prebuiltui}');
});
onUnmounted(() => {
const script = document.getElementById('supertokens-script');
if (script) {
script.remove();
}
});
},
});
</script>
<template>
<div id="supertokensui" />
</template>
#
Disable Network InterceptorsThe Authorization Service Frontend that you are configuring makes use of two types of access tokens:
- SuperTokens Session Access Token: Used only during the login flow in order to keep track of the authentication state.
- OAuth2 Access Token: Returned after a successfull login attempt. It can be used afterwards to access protected resources.
By default, the SuperTokens frontend SDK will intercept all the network requests that you send to your Backend API and adjust them, based on the SuperTokens Session Tokens. This allows us to perform operations, such as automatic token refreshing or adding authorization headers, without you having to configure anything else.
Given that in the scenario that you are implementing the OAuth2 Access Tokens are used for authorization.
The automatic request interception will end up causing conflicts.
In order to prevent this you will have to override the shouldDoInterceptionBasedOnUrl
function in the Session.init
call.
caution
The code samples assume that you are using /auth
as the apiBasePath
for the backend authentication routes.
If that is different please adjust them based on your use case.
- React
- Angular
- Vue
import Session from "supertokens-auth-react/recipe/session";
Session.init({
override: {
functions: (oI) => {
return {
...oI,
shouldDoInterceptionBasedOnUrl: (url, apiDomain, sessionTokenBackendDomain) => {
try {
let urlObj = new URL(url);
// Interception should be done only for routes that need the SuperTokens Session Tokens
const isAuthApiRoute = urlObj.pathname.startsWith("/auth");
const isOAuth2ApiRoute = urlObj.pathname.startsWith("/auth/oauth");
if (!isAuthApiRoute || isOAuth2ApiRoute) {
return false;
}
} catch (ignored) { }
return oI.shouldDoInterceptionBasedOnUrl(url, apiDomain, sessionTokenBackendDomain);
}
}
}
}
})
You will have to make changes to the auth route config, as well as to the supertokens-web-js
SDK config at the root of your application:
This change is in your auth route config.
// this goes in the auth route config of your frontend app (once the pre built UI script has been loaded)
import (window as any).supertokensUISession from "supertokens-auth-react/recipe/session";
(window as any).supertokensUISession.init({
override: {
functions: (oI) => {
return {
...oI,
shouldDoInterceptionBasedOnUrl: (url, apiDomain, sessionTokenBackendDomain) => {
try {
let urlObj = new URL(url);
if (!urlObj.pathname.startsWith("/auth")) {
return false;
}
} catch (ignored) { }
return oI.shouldDoInterceptionBasedOnUrl(url, apiDomain, sessionTokenBackendDomain);
}
}
}
}
})
This change goes in the supertokens-web-js
SDK config at the root of your application:
import Session from "supertokens-web-js/recipe/session";
Session.init({
override: {
functions: (oI) => {
return {
...oI,
shouldDoInterceptionBasedOnUrl: (url, apiDomain, sessionTokenBackendDomain) => {
try {
let urlObj = new URL(url);
if (!urlObj.pathname.startsWith("/auth")) {
return false;
}
} catch (ignored) { }
return oI.shouldDoInterceptionBasedOnUrl(url, apiDomain, sessionTokenBackendDomain);
}
}
}
}
})
You will have to make changes to the auth route config, as well as to the supertokens-web-js
SDK config at the root of your application:
This change is in your auth route config.
// this goes in the auth route config of your frontend app (once the pre built UI script has been loaded)
import (window as any).supertokensUISession from "supertokens-auth-react/recipe/session";
(window as any).supertokensUISession.init({
override: {
functions: (oI) => {
return {
...oI,
shouldDoInterceptionBasedOnUrl: (url, apiDomain, sessionTokenBackendDomain) => {
try {
let urlObj = new URL(url);
if (!urlObj.pathname.startsWith("/auth")) {
return false;
}
} catch (ignored) { }
return oI.shouldDoInterceptionBasedOnUrl(url, apiDomain, sessionTokenBackendDomain);
}
}
}
}
})
This change goes in the supertokens-web-js
SDK config at the root of your application:
import Session from "supertokens-web-js/recipe/session";
Session.init({
override: {
functions: (oI) => {
return {
...oI,
shouldDoInterceptionBasedOnUrl: (url, apiDomain, sessionTokenBackendDomain) => {
try {
let urlObj = new URL(url);
if (!urlObj.pathname.startsWith("/auth")) {
return false;
}
} catch (ignored) { }
return oI.shouldDoInterceptionBasedOnUrl(url, apiDomain, sessionTokenBackendDomain);
}
}
}
}
})
The code snippet only allows interception for API endpoints that start with /auth
.
This will ensure that calls made from our Frontend SDKs will continue to use the SuperTokens Session Tokens. As a result, the authentication flow ends up working properly.
For the other routes, you have full control on how you want to attach the OAuth2 Access Tokens to the API calls.
#
5. Update the login flow in your frontend applicationsUse a generic OAuth2 library to handle the login flow
- React
- Angular
- Vue
We recommend using the react-oidc-context library. Just follow the instructions from the library's page.
The configuration parameters can be determined based on the response that we received on step 2, when we created the OAuth2 Client.
authority
corresponds to the endpoint of the Authorization Service<YOUR_API_DOMAIN>/auth
clientID
corresponds toclientId
redirectUri
corresponds to a value fromcallbackUrls
scope
corresponds toscope
If you are using a multi-tenant setup, you will also need to specify the tenantId
parameter in the authorization URL.
To do this just set the extraQueryParams
property with a specific value that should look like this: { tenant_id: "<TENANT_ID>" }
.
We recommend using the angular-oauth2-oidc library. Just follow the instructions escribed here. The configuration parameters can be determined based on the response that we received on step 2, when we created the OAuth2 Client.
issuer
corresponds to the endpoint of the Authorization Service<YOUR_API_DOMAIN>/auth
client_id
corresponds toclientId
redirect_uri
corresponds to a value fromcallbackUrls
scope
corresponds toscope
If you are using a multi-tenant setup, you will also need to specify the tenantId
parameter in the authorization URL.
To do this just set the customQueryParams
property with a specific value that should look like this: { tenant_id: "<TENANT_ID>" }
.
We recommend using the oidc-client-ts library. Just follow the instructions described here. The configuration parameters can be determined based on the response that we received on step 2, when we created the OAuth2 Client.
issuer
corresponds to the endpoint of the Authorization Service<YOUR_API_DOMAIN>/auth
client_id
corresponds toclientId
redirect_uri
corresponds to a value fromcallbackUrls
scope
corresponds toscope
If you are using a multi-tenant setup, you will also need to specify the tenantId
parameter in the authorization URL.
To do this just set the extraQueryParams
property with a specific value that should look like this: { tenant_id: "<TENANT_ID>" }
.
info
If you want to use the OAuth2 Refresh Tokens make sure to include the offline_access
scope during the initialization step.
#
6. Test the new authentication flowWith everything set up, you can now test your login flow. Just use the setup that you have created in the previous step to check if the authentication flow completes without any issues.
For each of your frontend
applications you will have to create a separate OAuth2 client.
This can be done by directly calling the SuperTokens Core API.
# You will have to run this for each one of your applications
# Adjust the clientName and redirectUri based on that
curl -X POST /recipe/oauth/clients \
-H "Content-Type: application/json" \
-H "api-key: " \
-d '{
"clientName": "<YOUR_CLIENT_NAME>",
"responseTypes": ["code"],
"grantTypes": ["authorization_code", "refresh_token"],
"tokenEndpointAuthMethod": "none",
"scope": "offline_access <custom_scope_1> <custom_scope_2>",
"redirectUri": ["https://<YOUR_APPLICATION_DOMAIN>/oauth/callback"],
}'
clientName
- A human-readable name of the client that will be used for identification.responseTypes
- Specifies the types of responses your client expects from the Authorization Server.code
: Indicates that the Client will receive an Authorization Code that will be exchanged for an OAuth2 Access Token.
grantTypes
- The grant types that the Client will use.authorization_code
: Allows exchanging the Authorization Code for an OAuth2 Access Token.refresh_token
: Allows exchanging the OAuth2 Refresh Token for a new OAuth2 Access Token.
tokenEndpointAuthMethod
- Indicates that the process of obtaining an OAuth2 Access Token will not make use of the client secretredirectUri
- A list of URIs to which the Authorization Server will send the user-agent (browser) after completing the authorization step. These can be deep links to mobile or desktop apps as well, but they must be exact URLs, without wildcards.scope
- A space separated string of scopes that the Client will request access to.offline_access
: You need to include this scope if you want to use the OAuth2 Refresh Token to get a new OAuth2 Access Token.
If the creation was successful, the API will return a response that looks like this:
{
"clientName": "<YOUR_CLIENT_NAME>",
"clientId": "<CLIENT_ID>",
"callbackUrls": ["https://<YOUR_APPLICATION_DOMAIN>/oauth/callback"],
}
caution
You will have to save this response because we do not persist it internally for security reasons. In the next steps we will use the values to complete several configurations.
#
Change the default token lifespanBy default, the tokens used in the authorization flow will have the following lifespans:
- OAuth2 Access Token: 1 hour
- OAuth2 ID Token: 1 hour
- OAuth2 Refresh Token: 30 days
If you want to change the default values you need to specify additional properties in the Client creation request body.
Use string values that signify time duration in milliecoseconds, seconds, minutes or hours (e.g. "2000ms"
, "60s"
, "30m"
, "1h"
).
There are no limits on the duration of each token.
- OAuth2 Access Token - Set the
authorizationCodeGrantAccessTokenLifespan
property. - OAuth2 ID Token - Set the
authorizationCodeGrantIdTokenLifespan
property. - OAuth2 Refresh Token - Set both the
authorizationCodeGrantRefreshTokenLifespan
and therefreshTokenGrantRefreshTokenLifespan
properties to the same value.
#
Disable Refresh Token RotationBy default, the OAuth2 Refresh Token wil expire after 30 days. If your use case cannot accomodate the process of changing the OAuth2 Refresh Token for a new one, you can make it so that this behavior does not apply for your implementation.
In order to achieve this behavior just set the enableRefreshTokenRotation
property to false
in the Client creation request body.
#
3. Set Up your Authorization Service Backend#
Initialize the OAuth2 RecipesIn your Authorization Service you will need to initialize the OAuth2Provider recipe.
- NodeJS
- GoLang
- Python
Important
Update the supertokens.init
call to include the new recipe.
import supertokens from "supertokens-node";
import OAuth2Provider from "supertokens-node/recipe/oauth2provider";
supertokens.init({
supertokens: {
connectionURI: "...",
apiKey: "...",
},
appInfo: {
appName: "...",
apiDomain: "...",
websiteDomain: "...",
},
recipeList: [
OAuth2Provider.init(),
]
});
caution
At the moment we do not have support creating OAuth2 providers in the Go SDK.
caution
At the moment we do not have support creating OAuth2 providers in the Python SDK.
#
Update the CORS configurationYou need to setup the Backend API so that it allows requests from all the frontend domains.
- NodeJS
- GoLang
- Python
Important
import express from "express";
import cors from "cors";
import supertokens from "supertokens-node";
import { middleware } from "supertokens-node/framework/express";
const app = express();
// Add your actual frontend domains here
const allowedOrigins = ["<YOUR_WEBSITE_DOMAIN>", "<CLIENT_DOMAIN_1>", "<CLIENT_DOMAIN_2>"];
app.use(cors({
origin: allowedOrigins,
allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()],
credentials: true,
}));
caution
At the moment we do not have support creating OAuth2 providers in the Go SDK.
caution
At the moment we do not have support creating OAuth2 providers in the Python SDK.
#
Implement a Custom Session Verification FunctionGiven that the backend that represents the Authorization Server will also act as a Resource Server we will have to account for this in the way we verify the sessions.
This is needed because we are using two types of tokens:
- SuperTokens Session Access Token: Used during the login/logout flows.
- OAuth2 Access Token: Used to access protected resources and perform actions that need authorization.
Hence we need a way to distinguish between these two and prevent errors.
- NodeJS
- GoLang
- Python
Important
Here is an example of how to implement this in the context of an Express API:
import supertokens from "supertokens-node";
import Session from "supertokens-node/recipe/session";
import express, { Request, Response, NextFunction } from 'express';
import jose from "jose";
interface RequestWithUserId extends Request {
userId?: string;
}
async function verifySession(req: RequestWithUserId, res: Response, next: NextFunction) {
let session = undefined;
try {
session = await Session.getSession(req, res, { sessionRequired: false });
} catch (err) {
if (
!Session.Error.isErrorFromSuperTokens(err) ||
err.type !== Session.Error.TRY_REFRESH_TOKEN
) {
return next(err);
}
}
// In this case we are dealing with a SuperTokens Session
if (session !== undefined) {
const userId = session.getUserId();
req.userId = userId;
return next();
}
// The OAuth2 Access Token needs to be manually extracted and validated
let jwt: string | undefined = undefined;
if (req.headers["authorization"]) {
jwt = req.headers["authorization"].split("Bearer ")[1];
}
if (jwt === undefined) {
return next(new Error("No JWT found in the request"));
}
try {
const tokenPayload = await validateToken(jwt, '<CUSTOM_SCOPE>');
const userId = tokenPayload.sub;
req.userId = userId;
return next();
} catch (err) {
return next(err);
}
}
const JWKS = jose.createRemoteJWKSet(
new URL("<YOUR_API_DOMAIN>/authjwt/jwks.json"),
);
// This is a basic example on how to validate an OAuth2 Token
// We have a separate page that talks more in depth about the process
async function validateToken(jwt: string, requiredScope: string) {
const { payload } = await jose.jwtVerify(jwt, JWKS, {
requiredClaims: ["stt", "scp", "sub"],
});
if (payload.stt !== 1) throw new Error("Invalid token");
const scopes = payload.scp as string[];
if (!scopes.includes(requiredScope)) throw new Error("Invalid token");
return payload;
}
// You can then use the function as a middleware for a protected route
const app = express();
app.get("/protected", verifySession, async (req, res) => {
// Custom logic
});
For more information on how to verify the OAuth2 Access Tokens please check our separate guide.
caution
At the moment we do not have support creating OAuth2 providers in the Go SDK.
caution
At the moment we do not have support creating OAuth2 providers in the Python SDK.
#
4. Configure the Authorization Service FrontendInitialize the OAuth2Provider
recipe on the frontend of your Authorization Service.
info
If you want to use your own custom UI check our separate guide that explains all the steps that you have to take into account.
#
Initialize the Recipe- React
- Angular
- Vue
Add the import statement for the new recipe and update the list of recipe to also include the new initialization.
import OAuth2Provider from "supertokens-auth-react/recipe/oauth2provider";
import SuperTokens from "supertokens-auth-react";
SuperTokens.init({
appInfo: {
appName: "...",
apiDomain: "...",
websiteDomain: "...",
},
recipeList: [
OAuth2Provider.init()
]
});
#
Include the pre built UI in the rendering tree.Update the AuthComponent
so that it also includes the OAuth2Provider
recipe.
You will have to add a new item in the recipeList
array.
import {init as (window as any).supertokensUIInit} from "supertokens-auth-react";
import (window as any).supertokensUIOAuth2Provider from "supertokens-auth-react/recipe/oauth2provider";
import { Component, OnDestroy, AfterViewInit, Renderer2, Inject } from "@angular/core";
import { DOCUMENT } from "@angular/common";
@Component({
selector: "app-auth",
template: '<div id="supertokensui"></div>',
})
export class AuthComponent implements OnDestroy, AfterViewInit {
constructor(
private renderer: Renderer2,
@Inject(DOCUMENT) private document: Document
) { }
ngAfterViewInit() {
this.loadScript('^{jsdeliver_prebuiltui}');
}
ngOnDestroy() {
// Remove the script when the component is destroyed
const script = this.document.getElementById('supertokens-script');
if (script) {
script.remove();
}
}
private loadScript(src: string) {
const script = this.renderer.createElement('script');
script.type = 'text/javascript';
script.src = src;
script.id = 'supertokens-script';
script.onload = () => {
(window as any).supertokensUIInit("supertokensui", {
appInfo: {
appName: "<YOUR_APP_NAME>",
apiDomain: "<YOUR_API_DOMAIN>",
websiteDomain: "<YOUR_WEBSITE_DOMAIN>",
apiBasePath: "/auth",
websiteBasePath: "/auth"
},
recipeList: [
// Don't forget to also include the other recipes that you are already using
(window as any).supertokensUIOAuth2Provider.init()
],
});
}
this.renderer.appendChild(this.document.body, script);
}
}
Update the AuthView
component so that it also includes the OAuth2Provider
recipe.
You will have to add a new item in the recipeList
array, inside the (window as any).supertokensUIInit
call.
import {init as (window as any).supertokensUIInit} from "supertokens-auth-react";
import (window as any).supertokensUIOAuth2Provider from "supertokens-auth-react/recipe/oauth2provider";
<script lang="ts">
import { defineComponent, onMounted, onUnmounted } from 'vue';
export default defineComponent({
setup() {
const loadScript = (src: string) => {
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = src;
script.id = 'supertokens-script';
script.onload = () => {
(window as any).supertokensUIInit("supertokensui", {
appInfo: {
appName: "<YOUR_APP_NAME>",
apiDomain: "<YOUR_API_DOMAIN>",
websiteDomain: "<YOUR_WEBSITE_DOMAIN>",
apiBasePath: "/auth",
websiteBasePath: "/auth"
},
recipeList: [
// Don't forget to also include the other recipes that you are already using
(window as any).supertokensUIOAuth2Provider.init()
],
});
};
document.body.appendChild(script);
};
onMounted(() => {
loadScript('^{jsdeliver_prebuiltui}');
});
onUnmounted(() => {
const script = document.getElementById('supertokens-script');
if (script) {
script.remove();
}
});
},
});
</script>
<template>
<div id="supertokensui" />
</template>
#
Disable Network InterceptorsThe Authorization Service Frontend that you are configuring makes use of two types of access tokens:
- SuperTokens Session Access Token: Used only during the login flow in order to keep track of the authentication state.
- OAuth2 Access Token: Returned after a successfull login attempt. It can be used afterwards to access protected resources.
By default, the SuperTokens frontend SDK will intercept all the network requests that you send to your Backend API and adjust them, based on the SuperTokens Session Tokens. This allows us to perform operations, such as automatic token refreshing or adding authorization headers, without you having to configure anything else.
Given that in the scenario that you are implementing the OAuth2 Access Tokens are used for authorization.
The automatic request interception will end up causing conflicts.
In order to prevent this you will have to override the shouldDoInterceptionBasedOnUrl
function in the Session.init
call.
caution
The code samples assume that you are using /auth
as the apiBasePath
for the backend authentication routes.
If that is different please adjust them based on your use case.
- React
- Angular
- Vue
import Session from "supertokens-auth-react/recipe/session";
Session.init({
override: {
functions: (oI) => {
return {
...oI,
shouldDoInterceptionBasedOnUrl: (url, apiDomain, sessionTokenBackendDomain) => {
try {
let urlObj = new URL(url);
// Interception should be done only for routes that need the SuperTokens Session Tokens
const isAuthApiRoute = urlObj.pathname.startsWith("/auth");
const isOAuth2ApiRoute = urlObj.pathname.startsWith("/auth/oauth");
if (!isAuthApiRoute || isOAuth2ApiRoute) {
return false;
}
} catch (ignored) { }
return oI.shouldDoInterceptionBasedOnUrl(url, apiDomain, sessionTokenBackendDomain);
}
}
}
}
})
You will have to make changes to the auth route config, as well as to the supertokens-web-js
SDK config at the root of your application:
This change is in your auth route config.
// this goes in the auth route config of your frontend app (once the pre built UI script has been loaded)
import (window as any).supertokensUISession from "supertokens-auth-react/recipe/session";
(window as any).supertokensUISession.init({
override: {
functions: (oI) => {
return {
...oI,
shouldDoInterceptionBasedOnUrl: (url, apiDomain, sessionTokenBackendDomain) => {
try {
let urlObj = new URL(url);
if (!urlObj.pathname.startsWith("/auth")) {
return false;
}
} catch (ignored) { }
return oI.shouldDoInterceptionBasedOnUrl(url, apiDomain, sessionTokenBackendDomain);
}
}
}
}
})
This change goes in the supertokens-web-js
SDK config at the root of your application:
import Session from "supertokens-web-js/recipe/session";
Session.init({
override: {
functions: (oI) => {
return {
...oI,
shouldDoInterceptionBasedOnUrl: (url, apiDomain, sessionTokenBackendDomain) => {
try {
let urlObj = new URL(url);
if (!urlObj.pathname.startsWith("/auth")) {
return false;
}
} catch (ignored) { }
return oI.shouldDoInterceptionBasedOnUrl(url, apiDomain, sessionTokenBackendDomain);
}
}
}
}
})
You will have to make changes to the auth route config, as well as to the supertokens-web-js
SDK config at the root of your application:
This change is in your auth route config.
// this goes in the auth route config of your frontend app (once the pre built UI script has been loaded)
import (window as any).supertokensUISession from "supertokens-auth-react/recipe/session";
(window as any).supertokensUISession.init({
override: {
functions: (oI) => {
return {
...oI,
shouldDoInterceptionBasedOnUrl: (url, apiDomain, sessionTokenBackendDomain) => {
try {
let urlObj = new URL(url);
if (!urlObj.pathname.startsWith("/auth")) {
return false;
}
} catch (ignored) { }
return oI.shouldDoInterceptionBasedOnUrl(url, apiDomain, sessionTokenBackendDomain);
}
}
}
}
})
This change goes in the supertokens-web-js
SDK config at the root of your application:
import Session from "supertokens-web-js/recipe/session";
Session.init({
override: {
functions: (oI) => {
return {
...oI,
shouldDoInterceptionBasedOnUrl: (url, apiDomain, sessionTokenBackendDomain) => {
try {
let urlObj = new URL(url);
if (!urlObj.pathname.startsWith("/auth")) {
return false;
}
} catch (ignored) { }
return oI.shouldDoInterceptionBasedOnUrl(url, apiDomain, sessionTokenBackendDomain);
}
}
}
}
})
The code snippet only allows interception for API endpoints that start with /auth
.
This will ensure that calls made from our Frontend SDKs will continue to use the SuperTokens Session Tokens. As a result, the authentication flow ends up working properly.
For the other routes, you have full control on how you want to attach the OAuth2 Access Tokens to the API calls.
#
5. Update the login flow in your frontend applicationsUse a generic OAuth2 library to handle the login flow
- React
- Angular
- Vue
We recommend using the react-oidc-context library. Just follow the instructions from the library's page.
The configuration parameters can be determined based on the response that we received on step 2, when we created the OAuth2 Client.
authority
corresponds to the endpoint of the Authorization Service<YOUR_API_DOMAIN>/auth
clientID
corresponds toclientId
redirectUri
corresponds to a value fromcallbackUrls
scope
corresponds toscope
If you are using a multi-tenant setup, you will also need to specify the tenantId
parameter in the authorization URL.
To do this just set the extraQueryParams
property with a specific value that should look like this: { tenant_id: "<TENANT_ID>" }
.
We recommend using the angular-oauth2-oidc library. Just follow the instructions escribed here. The configuration parameters can be determined based on the response that we received on step 2, when we created the OAuth2 Client.
issuer
corresponds to the endpoint of the Authorization Service<YOUR_API_DOMAIN>/auth
client_id
corresponds toclientId
redirect_uri
corresponds to a value fromcallbackUrls
scope
corresponds toscope
If you are using a multi-tenant setup, you will also need to specify the tenantId
parameter in the authorization URL.
To do this just set the customQueryParams
property with a specific value that should look like this: { tenant_id: "<TENANT_ID>" }
.
We recommend using the oidc-client-ts library. Just follow the instructions described here. The configuration parameters can be determined based on the response that we received on step 2, when we created the OAuth2 Client.
issuer
corresponds to the endpoint of the Authorization Service<YOUR_API_DOMAIN>/auth
client_id
corresponds toclientId
redirect_uri
corresponds to a value fromcallbackUrls
scope
corresponds toscope
If you are using a multi-tenant setup, you will also need to specify the tenantId
parameter in the authorization URL.
To do this just set the extraQueryParams
property with a specific value that should look like this: { tenant_id: "<TENANT_ID>" }
.
info
If you want to use the OAuth2 Refresh Tokens make sure to include the offline_access
scope during the initialization step.
#
6. Test the new authentication flowWith everything set up, you can now test your login flow. Just use the setup that you have created in the previous step to check if the authentication flow completes without any issues.