Introduction

OAuth2 integration

Configure your Authalla tenant and OAuth2 client to start a standards-based authorization code flow.


Create a client

If you have an Authalla account, you already have a default tenant. You can find the tenant id in the Admin UI under Tenants → (select tenant) (it appears as the monospaced id in the header).

  1. Go to OAuth Clients → Create Client.
  2. Select your default tenant, set a Client Name, and choose an Application Type.
  3. Add at least one Redirect URI (must match exactly what you send in the authorize request).
  4. Click Create Client and save your Client ID (and Client Secret if this is a confidential client; it is only shown once).
  5. Configure Allowed origins in Tenants → (select tenant) → API → Allowed origins for any browser-based app making API calls.
  6. In the post-create screen or Clients → (select client) → Integration, copy the AI Integration Assistant prompt into your AI agent to generate OAuth 2.1-compliant integration code. The prompt includes the discovery URL and client ID, but never the client secret.

Node.js (Express) example

The examples below use openid-client for OAuth2/OIDC and jose for JWT validation. They show handler and middleware structure only—wire them into your router and store state in a server-side session store.

startAuthorization handler (Authorization Code + PKCE)

import * as client from 'openid-client'

const issuer = new URL('https://{tenant-id}.authalla.com')
const clientId = 'client_123'
const redirectUri = 'https://app.example.com/oauth/callback'

export async function startAuthorization(req, res, next) {
  try {
    const config = await client.discovery(issuer, clientId)

    const codeVerifier = client.randomPKCECodeVerifier()
    const codeChallenge = await client.calculatePKCECodeChallenge(codeVerifier)
    const state = client.randomState()

    req.session.oauth = { codeVerifier, state }

    const authorizationUrl = client.buildAuthorizationUrl(config, {
      redirect_uri: redirectUri,
      scope: 'openid profile email',
      state,
      code_challenge: codeChallenge,
      code_challenge_method: 'S256',
    })

    res.redirect(authorizationUrl.href)
  } catch (error) {
    next(error)
  }
}

callback handler (exchange code for tokens)

import * as client from 'openid-client'

const issuer = new URL('https://{tenant-id}.authalla.com')
const clientId = 'client_123'

export async function callback(req, res, next) {
  try {
    const config = await client.discovery(issuer, clientId)
    const currentUrl = new URL(req.originalUrl, `${req.protocol}://${req.get('host')}`)

    const { codeVerifier, state } = req.session.oauth || {}
    const tokens = await client.authorizationCodeGrant(config, currentUrl, {
      pkceCodeVerifier: codeVerifier,
      expectedState: state,
    })

    const claims = tokens.claims()
    req.session.tokens = tokens
    req.session.user = { sub: claims.sub, email: claims.email }
    res.redirect('/app')
  } catch (error) {
    next(error)
  }
}

authorize middleware (validate access tokens)

import * as jose from 'jose'

const issuer = 'https://{tenant-id}.authalla.com'
const audience = 'https://api.example.com'
const jwks = jose.createRemoteJWKSet(new URL(`${issuer}/.well-known/jwks.json`))

export async function authorize(req, res, next) {
  try {
    const header = req.headers.authorization || ''
    if (!header.startsWith('Bearer ')) {
      return res.status(401).json({ error: 'missing_token' })
    }

    const token = header.slice('Bearer '.length)
    const { payload } = await jose.jwtVerify(token, jwks, { issuer, audience })
    req.auth = { sub: payload.sub, email: payload.email }
    return next()
  } catch (error) {
    return res.status(401).json({ error: 'invalid_token' })
  }
}

Custom domains: If you use a custom domain, your issuer and JWKS URL change to that domain. See tenant custom domains.

Need another language? Copy these examples into an LLM and ask it to translate the handlers and middleware into your framework and language of choice, including production-ready session storage, CSRF protection, and error handling.

Previous
Getting started