Skip to main content
Trusted Auth Tokens let you represent custom authentication factors in Stytch by signing your own JWTs and exchanging them for sessions. Typically, your existing identity infrastructure will provide a JWT that can be used for this purpose such as an access_token or id_token. Stytch will use the JWKS endpoint hosted by your existing infrastructure to validate these JWTs and provision users automatically as required — no additional setup is needed. If your identity infrastructure does not provide a JWT, you can create your own JWT using a private key.

Minting a JWT and logging in

1

Generate a public-private key pair

Use a library such as OpenSSL to generate an RSA public-private keypair.The public key can be added to your Trusted Auth Token profile in the Stytch dashboard here; (in the JWKS URL row, click “Use public keys instead”).
The private key should be kept secret, and exposed to your application using an environment variable.
It is often useful to base64 encode the private key for easy addition to .env files.
# Generate private key
openssl genrsa -out "private_key.pem" 2048
# Extract public key
openssl rsa -in "private_key.pem" -pubout -out "public_key.pem"
# Base64 encode the private key
base64 < ./private_key.pem
2

Create and sign a JWT

Use any compatible JWT library to sign your JWT with your private key.
Here is an example using jose, a popular library in the Node ecosystem.
Be sure to set the iss and aud claims to match the issuer and audience configured in your Trusted Auth Token Profile.
Choose a short expiry for the JWT - 5 minutes is a good default.
Other claims (email, sub, role_ids, jti, etc… ) should be set according to your previously configured attribute mapping.
Remember, an email and a token ID are required. All other fields are optional.
import { importPKCS8, SignJWT } from 'jose';
import { randomBytes } from 'node:crypto';

async function createJWT({email, role_assignments, user_id, token_id}) {
  // Expose the private key as an environment variable
  // If you base64 encoded it before, remember to decode it here as well
  const privateKeyString = Buffer.from(process.env.PRIVATE_KEY, 'base64')
    .toString('utf8').trim();
  const privateKey = await importPKCS8(privateKeyString, 'RS256');

  // details to encode in the token
  const claims = {
    // Required - the user's email address
    'email': email,
    // Optional - RBAC roles to assign to the user
    'assignments': role_assignments,
  };
  
  // Required - a token ID used to uniquely identify the credential
  // This can be defined by your application or a random string
  const tokenID = token_id || randomBytes(30).toString('base64');
  
  const jwt = await new SignJWT(claims)
    .setProtectedHeader({ alg: 'RS256' })
    .setIssuedAt()
    .setSubject(user_id) // sub claim, used as the user's external ID
    .setJti(tokenID)
    .setIssuer('https://auth.example.com') // iss claim
    .setAudience('https://api.example.com') // aud claim
    .setExpirationTime('5 minutes') // token expiration time
    .sign(privateKey);

  return jwt;
};

createJWT({
  email: 'ada.lovelace@example.com',
  role_assignments: ['editor', 'reader'],
  user_id: 'user_123456'
}).then(jwt => console.log(jwt))
3

Logging a user in

After you have minted your JWT, call the Attest Session API endpoint to exchange the JWT for a Stytch Session.
const stytch = require('stytch');

const client = new stytch.Client({
  project_id: 'PROJECT_ID',
  secret: 'SECRET',
});

createJWT({
  email: 'ada.lovelace@example.com',
  role_assignments: ['editor', 'reader'],
  user_id: 'user_123456'
})
  .then(jwt => {
    const params = {
      profile_id: "trusted-auth-token-profile-...",
      token: "eyJhb...",
    };
    
    return client.sessions.attest(params)
  })
  .then(resp => {
    console.log(resp)
  })
  .catch(err => {
    console.log(err)
  });

Adding custom auth factors

To add custom auth factors to an existing flow, call the Attest Session API endpoint with a session_token or session_jwt to attach the attestation to an existing session instead of creating a new one. It may be useful to pass in a unique token_id to the JWT to tie the custom authentication factor you validated to the session for later reference.
validateCustomFactor()
  .then(validation => createJWT({
    email: 'ada.lovelace@example.com',
    role_assignments: ['editor', 'reader'],
    user_id: 'user_123456',
    // ID of the validation event - will be passed to the Stytch session as
    // trusted_auth_token_factor.token_id
    token_id: validation.validation_id,
  }))
  .then(jwt => {
    const params = {
      profile_id: "trusted-auth-token-profile-...",
      token: "eyJhb...",
      // Pass a session token in to add your custom factor to an existing session
      session_token: "SESSION_TOKEN",
    };
    
    return client.sessions.attest(params)
  })
  .then(resp => {
    console.log(resp)
  })
  .catch(err => {
    console.log(err)
  });