Introduction
OAuth2 integration
Integrate Authalla authentication into your app. The fastest way is to let your AI assistant handle it — or follow the manual steps below.
Integrate with AI (recommended)
Connect Authalla's MCP server to your AI coding assistant. It can create OAuth2 clients, configure redirect URIs, and generate integration code for your framework — all in one conversation.
1. Add the Authalla agent skill
Install the Authalla agent skill to give your AI assistant knowledge about Authalla — it learns how to integrate authentication, configure tenants, and generate correct OAuth2 flows for any framework.
npx skills add authalla/agent-skills
2. Connect the MCP server
Add the Authalla MCP server so your assistant can manage your tenant directly — creating clients, configuring domains, setting up social login, and more:
# Claude Code
claude mcp add authalla https://login.authalla.com/mcp
For other clients (Claude Desktop, VS Code, Cursor), see MCP server setup.
3. Ask your assistant to set up auth
Once connected, describe your app and your assistant will handle the rest:
> I'm building a Next.js app running on localhost:3000. Create an OAuth2
client and show me how to integrate Authalla login with the app router.
> Set up Authalla authentication for my Express API. I need login, callback,
and a middleware that validates access tokens on protected routes.
> I have a React SPA on localhost:5173. Create a public OAuth2 client and
generate the PKCE authorization flow with token refresh.
Your assistant has access to all Authalla management tools — it will create the client, set the redirect URIs and allowed origins, and generate working integration code tailored to your stack.
4. Configure additional features
You can keep going in the same conversation:
> Enable Google social login on my tenant
> Set up a custom domain auth.myapp.com
> Update the login page to match my brand colors
Integrate manually
If you prefer to set things up by hand, follow the steps below.
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).
- Go to OAuth Clients → Create Client.
- Select your default tenant, set a Client Name, and choose an Application Type.
- Add at least one Redirect URI (must match exactly what you send in the authorize request).
- Click Create Client and save your Client ID (and Client Secret if this is a confidential client; it is only shown once).
- Configure Allowed origins in Tenants → (select tenant) → API → Allowed origins for any browser-based app making API calls.
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.