Dependencies
Copy
Ask AI
npm install jsonwebtoken
Function
Create a file underfunctions (for instance /functions/custom-jwts.ts), with the following contents:
Copy
Ask AI
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,
        },
    )
}
Copy
Ask AI
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"}
Copy
Ask AI
{
  "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"
}