Creating custom JWTs
In some cases, it’s necessary to act on behalf of a user. While the Auth service doesn’t support this directly, it’s not difficult to implement using a serverless function. Below is an example of a function that generates a valid access token for your application with customized values. For details, see the function’s docstring.
Feel free to adapt to your needs.
npm install jsonwebtoken
Create a file under functions
(for instance /functions/custom-jwts.ts
), with the following contents:
import { Request, Response } from 'express'
import process from 'process'
import jwt from 'jsonwebtoken'
// function to extract jwt from the request
const getJwt = (req: Request): string | null => {
const authHeader = req.headers.authorization
if (!authHeader) {
return null
}
const parts = authHeader.split(' ')
if (parts.length !== 2) {
return null
}
const [scheme, token] = parts
if (!/^Bearer$/i.test(scheme)) {
return null
}
return token
}
// validate jwt token is valid and caller is either an admin or an operator
const jwtIsAuthorized = (req: Request, key: string): string => {
const token = getJwt(req)
if (!token) {
return ""
}
try {
const decoded = jwt.verify(token, key)
const claims = decoded['https://hasura.io/jwt/claims']
if ( !claims || !claims['x-hasura-allowed-roles'] ) {
return ""
}
if (
claims['x-hasura-allowed-roles'].includes('admin') ||
claims['x-hasura-allowed-roles'].includes('operator')
) {
return decoded.sub
}
return ""
} catch (e) {
return ""
}
}
/*
This is a sample function that generates a JWT token to impersonate users.
Authorization:
- send a valid JWT token with the request. The token should have the `admin` or `operator` role.
- send the `x-hasura-admin-secret` header with the request
Body:
A json object with the following keys:
- `userId` (string): the user id for which the token is generated
- `defaultRole` (string): the default role for the userId
- `allowedRoles` (array of strings): the roles that the userId can assume
Returns:
A json object with the following keys:
- `accessToken` (string) - The generated access token
In addition to the typical JWT claims generated by Nhost, the token generated by this function will have the following claims:
- `x-hasura-on-behalf-of`: the user id of the caller or `admin` if the caller used the `x-hasura-admin-secret` header
You are free to modify this function to suit your needs.
*/
export default (req: Request, res: Response) => {
let authorizedCaller = ""
if (req.headers['x-hasura-admin-secret'] === process.env.HASURA_GRAPHQL_ADMIN_SECRET) {
authorizedCaller = "admin"
}
const jwtSecret = JSON.parse(process.env.NHOST_JWT_SECRET)
if (!authorizedCaller) {
authorizedCaller = jwtIsAuthorized(req, jwtSecret.key)
}
if (!authorizedCaller) {
return res.status(401).json({ message: 'Unauthorized' })
}
// extract from json in the body
const {userId, defaultRole, allowedRoles} = req.body
if (!userId || !defaultRole || !allowedRoles) {
return res.status(400).json({ message: 'Bad request' })
}
let token = jwt.sign({
"exp": Math.floor(Date.now() / 1000) + 60 * 60, // 1 hour
"https://hasura.io/jwt/claims": {
"x-hasura-allowed-roles": allowedRoles,
"x-hasura-default-role": defaultRole,
"x-hasura-user-id": userId,
"x-hasura-user-is-anonymous": "false",
"x-hasura-on-behalf-of": authorizedCaller
},
"iat": Math.floor(Date.now() / 1000),
"iss": "custom-lambda",
"sub": userId,
}, jwtSecret.key);
res.status(200).json(
{
accessToken: token,
},
)
}
Now you can call it like:
curl -X POST \
-H "Content-Type: application/json" \
-H "x-hasura-admin-secret: nhost-admin-secret" \
-d '{"userId": "FFAB5354-C5EB-42C1-8BC3-AD21D2297883", "defaultRole": "user", "allowedRoles": ["user", "me"]}' \
https://local.functions.local.nhost.run/v1/custom-jwt
{"accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTcxNDIyMTMsImh0dHBzOi8vaGFzdXJhLmlvL2p3dC9jbGFpbXMiOnsieC1oYXN1cmEtYWxsb3dlZC1yb2xlcyI6WyJ1c2VyIiwibWUiXSwieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoidXNlciIsIngtaGFzdXJhLXVzZXItaWQiOiJGRkFCNTM1NC1DNUVCLTQyQzEtOEJDMy1BRDIxRDIyOTc4ODMiLCJ4LWhhc3VyYS11c2VyLWlzLWFub255bW91cyI6ImZhbHNlIiwieC1oYXN1cmEtb24tYmVoYWxmLW9mIjoiYWRtaW4ifSwiaWF0IjoxNzE3MTM4NjEzLCJpc3MiOiJjdXN0b20tbGFtYmRhIiwic3ViIjoiRkZBQjUzNTQtQzVFQi00MkMxLThCQzMtQUQyMUQyMjk3ODgzIn0.bRhzJvXMdkQA8aXPH95uMT17WHED2rSRq3gE21Vp3Ak"}
The new token should be a valid token for your application with the custom values requested:
{
"exp": 1717141288,
"https://hasura.io/jwt/claims": {
"x-hasura-allowed-roles": ["a", "b"],
"x-hasura-default-role": "user",
"x-hasura-user-id": "FFAB5354-C5EB-42C1-8BC3-AD21D2297883",
"x-hasura-user-is-anonymous": "false",
"x-hasura-on-behalf-of": "admin"
},
"iat": 1717137688,
"iss": "custom-lambda",
"sub": "FFAB5354-C5EB-42C1-8BC3-AD21D2297883"
}
Was this page helpful?
Creating custom JWTs
In some cases, it’s necessary to act on behalf of a user. While the Auth service doesn’t support this directly, it’s not difficult to implement using a serverless function. Below is an example of a function that generates a valid access token for your application with customized values. For details, see the function’s docstring.
Feel free to adapt to your needs.
npm install jsonwebtoken
Create a file under functions
(for instance /functions/custom-jwts.ts
), with the following contents:
import { Request, Response } from 'express'
import process from 'process'
import jwt from 'jsonwebtoken'
// function to extract jwt from the request
const getJwt = (req: Request): string | null => {
const authHeader = req.headers.authorization
if (!authHeader) {
return null
}
const parts = authHeader.split(' ')
if (parts.length !== 2) {
return null
}
const [scheme, token] = parts
if (!/^Bearer$/i.test(scheme)) {
return null
}
return token
}
// validate jwt token is valid and caller is either an admin or an operator
const jwtIsAuthorized = (req: Request, key: string): string => {
const token = getJwt(req)
if (!token) {
return ""
}
try {
const decoded = jwt.verify(token, key)
const claims = decoded['https://hasura.io/jwt/claims']
if ( !claims || !claims['x-hasura-allowed-roles'] ) {
return ""
}
if (
claims['x-hasura-allowed-roles'].includes('admin') ||
claims['x-hasura-allowed-roles'].includes('operator')
) {
return decoded.sub
}
return ""
} catch (e) {
return ""
}
}
/*
This is a sample function that generates a JWT token to impersonate users.
Authorization:
- send a valid JWT token with the request. The token should have the `admin` or `operator` role.
- send the `x-hasura-admin-secret` header with the request
Body:
A json object with the following keys:
- `userId` (string): the user id for which the token is generated
- `defaultRole` (string): the default role for the userId
- `allowedRoles` (array of strings): the roles that the userId can assume
Returns:
A json object with the following keys:
- `accessToken` (string) - The generated access token
In addition to the typical JWT claims generated by Nhost, the token generated by this function will have the following claims:
- `x-hasura-on-behalf-of`: the user id of the caller or `admin` if the caller used the `x-hasura-admin-secret` header
You are free to modify this function to suit your needs.
*/
export default (req: Request, res: Response) => {
let authorizedCaller = ""
if (req.headers['x-hasura-admin-secret'] === process.env.HASURA_GRAPHQL_ADMIN_SECRET) {
authorizedCaller = "admin"
}
const jwtSecret = JSON.parse(process.env.NHOST_JWT_SECRET)
if (!authorizedCaller) {
authorizedCaller = jwtIsAuthorized(req, jwtSecret.key)
}
if (!authorizedCaller) {
return res.status(401).json({ message: 'Unauthorized' })
}
// extract from json in the body
const {userId, defaultRole, allowedRoles} = req.body
if (!userId || !defaultRole || !allowedRoles) {
return res.status(400).json({ message: 'Bad request' })
}
let token = jwt.sign({
"exp": Math.floor(Date.now() / 1000) + 60 * 60, // 1 hour
"https://hasura.io/jwt/claims": {
"x-hasura-allowed-roles": allowedRoles,
"x-hasura-default-role": defaultRole,
"x-hasura-user-id": userId,
"x-hasura-user-is-anonymous": "false",
"x-hasura-on-behalf-of": authorizedCaller
},
"iat": Math.floor(Date.now() / 1000),
"iss": "custom-lambda",
"sub": userId,
}, jwtSecret.key);
res.status(200).json(
{
accessToken: token,
},
)
}
Now you can call it like:
curl -X POST \
-H "Content-Type: application/json" \
-H "x-hasura-admin-secret: nhost-admin-secret" \
-d '{"userId": "FFAB5354-C5EB-42C1-8BC3-AD21D2297883", "defaultRole": "user", "allowedRoles": ["user", "me"]}' \
https://local.functions.local.nhost.run/v1/custom-jwt
{"accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTcxNDIyMTMsImh0dHBzOi8vaGFzdXJhLmlvL2p3dC9jbGFpbXMiOnsieC1oYXN1cmEtYWxsb3dlZC1yb2xlcyI6WyJ1c2VyIiwibWUiXSwieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoidXNlciIsIngtaGFzdXJhLXVzZXItaWQiOiJGRkFCNTM1NC1DNUVCLTQyQzEtOEJDMy1BRDIxRDIyOTc4ODMiLCJ4LWhhc3VyYS11c2VyLWlzLWFub255bW91cyI6ImZhbHNlIiwieC1oYXN1cmEtb24tYmVoYWxmLW9mIjoiYWRtaW4ifSwiaWF0IjoxNzE3MTM4NjEzLCJpc3MiOiJjdXN0b20tbGFtYmRhIiwic3ViIjoiRkZBQjUzNTQtQzVFQi00MkMxLThCQzMtQUQyMUQyMjk3ODgzIn0.bRhzJvXMdkQA8aXPH95uMT17WHED2rSRq3gE21Vp3Ak"}
The new token should be a valid token for your application with the custom values requested:
{
"exp": 1717141288,
"https://hasura.io/jwt/claims": {
"x-hasura-allowed-roles": ["a", "b"],
"x-hasura-default-role": "user",
"x-hasura-user-id": "FFAB5354-C5EB-42C1-8BC3-AD21D2297883",
"x-hasura-user-is-anonymous": "false",
"x-hasura-on-behalf-of": "admin"
},
"iat": 1717137688,
"iss": "custom-lambda",
"sub": "FFAB5354-C5EB-42C1-8BC3-AD21D2297883"
}
Was this page helpful?