Using JWT Authorizers
caution
AWS supports JWT authorizers for HTTP APIs and not REST APIs on the API Gateway service. For REST APIs follow the Lambda authorizer guide
This guide will work if you are using SuperTokens Session Tokens.
If you implementing an OAuth2 setup, through the Unified Login or the Microservice Authentication features, you will have to manually set the token audience property. Please check the referenced pages for more information.
aud
claim in the JWT based on the authorizer configuration#
1) Add the - Single app setup
- Multi app setup
- NodeJS
- Python
- Other Frameworks
Important
import Session from 'supertokens-node/recipe/session'
export function getBackendConfig() {
return {
framework: "awsLambda",
supertokens: {
connectionURI: "",
apiKey: "",
},
appInfo: {
// learn more about this on https://supertokens.com/docs/session/appinfo
appName: "<YOUR_APP_NAME>",
apiDomain: "<YOUR_API_DOMAIN>",
websiteDomain: "<YOUR_WEBSITE_DOMAIN>",
apiBasePath: "/auth",
websiteBasePath: "/auth",
},
recipeList: [
Session.init({
exposeAccessTokenToFrontendInCookieBasedAuth: true,
override: {
functions: function (originalImplementation) {
return {
...originalImplementation,
createNewSession: async function (input) {
input.accessTokenPayload = {
...input.accessTokenPayload,
/*
* AWS requires JWTs to contain an audience (aud) claim
* The value for this claim should be the same
* as the value you set when creating the
* authorizer
*/
aud: "jwtAuthorizers",
};
return originalImplementation.createNewSession(input);
},
};
}
},
}),
],
isInServerlessEnv: true,
}
}
from supertokens_python.recipe import session
from supertokens_python import (
InputAppInfo,
SupertokensConfig,
)
from supertokens_python.recipe.session.interfaces import RecipeInterface as SessionRecipeInterface
from typing import Any, Dict, Optional
from supertokens_python.types import RecipeUserId
supertokens_config = SupertokensConfig(
connection_uri="",
api_key=""
)
app_info = InputAppInfo(
# learn more about this on https://supertokens.com/docs/session/appinfo
app_name="<YOUR_APP_NAME>",
api_domain="<YOUR_API_DOMAIN>",
website_domain="<YOUR_WEBSITE_DOMAIN>",
api_base_path="/auth",
website_base_path="/auth",
api_gateway_path="/dev",
)
framework = "fastapi"
def override_session_functions(oi: SessionRecipeInterface) -> SessionRecipeInterface:
oi_create_new_session = oi.create_new_session
async def create_new_session(
user_id: str,
recipe_user_id: RecipeUserId,
access_token_payload: Optional[Dict[str, Any]],
session_data_in_database: Optional[Dict[str, Any]],
disable_anti_csrf: Optional[bool],
tenant_id: str,
user_context: Dict[str, Any],
):
# AWS requires JWTs to contain an audience (aud) claim
# The value for this claim should be the same as the
# value you set when creating the authorizer
if access_token_payload is None:
access_token_payload = {}
access_token_payload["aud"] = "jwtAuthorizers"
return await oi_create_new_session(user_id, recipe_user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context)
oi.create_new_session = create_new_session
return oi
recipe_list = [
session.init(
override=session.InputOverrideConfig(
functions=override_session_functions,
),
expose_access_token_to_frontend_in_cookie_based_auth=True,
),
]
- NodeJS
- Python
- Other Frameworks
Important
import Session from 'supertokens-node/recipe/session'
export function getBackendConfig() {
return {
framework: "awsLambda",
supertokens: {
connectionURI: "",
apiKey: "",
},
appInfo: {
// learn more about this on https://supertokens.com/docs/session/appinfo
appName: "<YOUR_APP_NAME>",
apiDomain: "<YOUR_API_DOMAIN>",
websiteDomain: "<YOUR_WEBSITE_DOMAIN>",
apiBasePath: "/auth",
websiteBasePath: "/auth",
},
recipeList: [
Session.init({
exposeAccessTokenToFrontendInCookieBasedAuth: true,
override: {
functions: function (originalImplementation) {
return {
...originalImplementation,
createNewSession: async function (input) {
input.accessTokenPayload = {
...input.accessTokenPayload,
/*
* AWS requires JWTs to contain an audience (aud) claim
* The value for this claim should be the same
* as the value you set when creating the
* authorizer
*/
aud: "jwtAuthorizers",
};
return originalImplementation.createNewSession(input);
},
};
}
},
}),
],
isInServerlessEnv: true,
}
}
from supertokens_python.recipe import session
from supertokens_python import (
InputAppInfo,
SupertokensConfig,
)
from supertokens_python.recipe.session.interfaces import RecipeInterface as SessionRecipeInterface
from typing import Any, Dict, Optional
from supertokens_python.types import RecipeUserId
supertokens_config = SupertokensConfig(
connection_uri="",
api_key=""
)
app_info = InputAppInfo(
# learn more about this on https://supertokens.com/docs/session/appinfo
app_name="<YOUR_APP_NAME>",
api_domain="<YOUR_API_DOMAIN>",
website_domain="<YOUR_WEBSITE_DOMAIN>",
api_base_path="/auth",
website_base_path="/auth",
api_gateway_path="/dev",
)
framework = "fastapi"
def override_session_functions(oi: SessionRecipeInterface) -> SessionRecipeInterface:
oi_create_new_session = oi.create_new_session
async def create_new_session(
user_id: str,
recipe_user_id: RecipeUserId,
access_token_payload: Optional[Dict[str, Any]],
session_data_in_database: Optional[Dict[str, Any]],
disable_anti_csrf: Optional[bool],
tenant_id: str,
user_context: Dict[str, Any],
):
# AWS requires JWTs to contain an audience (aud) claim
# The value for this claim should be the same as the
# value you set when creating the authorizer
if access_token_payload is None:
access_token_payload = {}
access_token_payload["aud"] = "jwtAuthorizers"
return await oi_create_new_session(user_id, recipe_user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context)
oi.create_new_session = create_new_session
return oi
recipe_list = [
session.init(
override=session.InputOverrideConfig(
functions=override_session_functions,
),
expose_access_token_to_frontend_in_cookie_based_auth=True,
),
]
#
2) Configure your authorizer- Go to the "Authorizers" tab in the API Gateway configuration and select the "Manage authorizers" tab
- Click "Create", in the creation screen select "JWT" as the "Authorizer type"
- Enter a name for your authorizer (You can enter any name for this field)
- Use
$request.header.Authorization
for the "Identity source". This means that API requests will contain the JWT as a Bearer token under the request header "Authorization". - Use
{apiDomain}/{apiGatewayPath}/{apiBasePath}
for the "Issuer URL". - Set a value for the "Audience" field, this will be the value you expect the JWT to have under the
aud
claim. In the backend config above the value is set to"jwtAuthorizers"
#
3) Add the authorizer to your API- In the "Authorization" section select the "Attach authorizers to routes" tab
- Click on the route you want to add the authorizer to and select the authorizer you created from the dropdown
- Click "Attach authorizer"
- Deploy your changes and test your API
#
4) Check for auth claims of the JWTOnce the JWT authorizer successfully validates the JWT, the claims of the JWT will be available to your lambda functions via $event.requestContext.authorizer.jwt.claims
. You should check for the right authorization access here.
For example, if one of your lambda functions requires that the user's email is verified, then it should check for the jwt payload's st-ev
claim value to be {v: true, t:...}
, else it should reject the request. Similar checks need to be done to enforce the right user role or if 2FA is completed or not.
This is required because SuperTokens issues JWTs immediately after the user signs up / logs in, regardless of if all the authorisation checks pass or not. Functions exposed by our SDK like verifySession
or getSession
do these authorisation checks on their own, but since these functions are not used in the this flow, you will have to check them on your own.