Custom JWTs
Generate custom JWTs to act on behalf of users
JWT custom tokens user impersonation serverless functions access tokens Hasura claimsIn some cases you need to act on behalf of a user — for example, to impersonate a user from an admin panel or to generate tokens with custom claims. The Auth service doesn’t support this directly, but you can implement it with a function.
Dependencies
Section titled “Dependencies”npm install jsonwebtokennpm install -D @types/jsonwebtokenFunction
Section titled “Function”Create a file at ./functions/custom-jwt.ts:
import type { Request, Response } from 'express'import process from 'node:process'import jwt from 'jsonwebtoken'
const getJwt = (req: Request): string | null => { const authHeader = req.headers.authorization if (!authHeader) return null
const parts = authHeader.split(' ') if (parts.length !== 2 || !/^Bearer$/i.test(parts[0])) return null
return parts[1]}
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?.['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 { return '' }}
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' }) }
const { userId, defaultRole, allowedRoles } = req.body if (!userId || !defaultRole || !allowedRoles) { return res.status(400).json({ message: 'Bad request' }) }
const token = jwt.sign( { exp: Math.floor(Date.now() / 1000) + 60 * 60, '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 })}Authorization
Section titled “Authorization”The function accepts two authorization methods:
- Admin secret: Send the
x-hasura-admin-secretheader - JWT: Send a valid JWT with the
adminoroperatorrole
Request Body
Section titled “Request Body”| Field | Type | Description |
|---|---|---|
userId | string | User ID to generate the token for |
defaultRole | string | Default role for the user |
allowedRoles | string[] | Roles the user can assume |
Example
Section titled “Example”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-jwtThe response contains a signed JWT with the custom claims, including x-hasura-on-behalf-of indicating who initiated the request.