Core authentication
User API
The User API is called by your app, with the signed-in user's own access token, about that user: their profile, the browsers they are signed in on, and the apps connected to their account. Use it to build "Where you're signed in" and "Apps with access to your account" into your product without a backend of your own.
It is Authalla's only REST API for a signed-in user: it takes the user's own access token and reaches only that user, so your app may call it from the browser. The others are called from your server with a secret credential (an account API key, or a client credentials token) that reaches every user of a tenant or the whole account, so that credential never belongs in a browser or a mobile app. When a user changes their own details, call the User API with their token rather than the Application API with yours. Choosing an API compares them.
Every operation is listed in the User API reference.
| You want to… | Call | Scope |
|---|---|---|
| Show the user's name, or change it | GET /me/v1, PATCH /me/v1 | profile |
| List the browsers the user is signed in on | GET /me/v1/sessions | sessions:read |
| Sign the user out of one of them | DELETE /me/v1/sessions/{id} | sessions:write |
| List the apps connected to the user's account | GET /me/v1/connected-apps | connected-apps:read |
| Revoke one of them | DELETE /me/v1/connected-apps/{id} | connected-apps:write |
Host
Call the User API on the tenant's own host, the one your app signs in through: https://{tenant}.authalla.com, or the tenant's custom domain. The tenant comes from the host, so an app on a custom domain calls that domain. api.authalla.com doesn't serve the User API.
A browser app may call it from the origins in the tenant's Allowed origins (Tenants → (select tenant) → General), as for the token endpoint.
Access token
Send the user's access token as Authorization: Bearer <access token>. Authalla checks the token on every call, as introspection does, because these calls end sessions and revoke apps. The token must be:
- an access token of this tenant, issued to your app for a signed-in user. An ID token is refused, and so is a
client_credentialstoken, which has no user; - issued for your app itself, requested without the
resourceparameter. A token issued for a resource server, such as your MCP server, is refused, so an agent holding one can't list or revoke the user's other apps; - still active: not expired, not revoked, and the user is
active.
A token that fails any of these gets 401 with WWW-Authenticate: Bearer error="invalid_token". A token without the operation's scope gets 403 with WWW-Authenticate: Bearer error="insufficient_scope". A session or app that isn't the user's, or no longer exists, is 404. Every error has the JSON shape described in Errors.
Scopes
Each operation needs one scope. sessions:read, sessions:write, connected-apps:read and connected-apps:write don't have to be granted to each user: any app that is allowed them can request them.
- Add the scopes the app needs to its allowed scopes: Apps → (select app) → Scopes, or the app's
scopesin the Management API. - Request them at sign-in, together with the others the app needs:
scope: 'openid profile sessions:read sessions:write connected-apps:read connected-apps:write'
Ask only for what the app shows. The consent screen says each scope in plain words, such as "See where you're signed in", and a read and write pair asked for together as one line, such as "See and end your browser sessions". The tenant's discovery document lists the four scopes in scopes_supported.
List and end sessions
const authority = 'https://acme.authalla.com'
async function userApi(path, accessToken, init = {}) {
const response = await fetch(`${authority}${path}`, {
...init,
headers: { ...init.headers, Authorization: `Bearer ${accessToken}` },
})
if (!response.ok) {
const { error, message } = await response.json()
throw new Error(`${response.status} ${error}: ${message}`)
}
return response.status === 204 ? null : response.json()
}
// Where you're signed in
const { sessions } = await userApi('/me/v1/sessions', accessToken)
for (const session of sessions) {
console.log(session.user_agent, session.created_at, session.current ? '(this browser)' : '')
}
// Sign out of one browser
await userApi(`/me/v1/sessions/${sessions[1].id}`, accessToken, { method: 'DELETE' })
Each session has its id, how the user signed in (auth_method), the browser's IP address and User-Agent at sign-in, and when it was created and ends. Ending a session signs that browser out: its next request to Authalla starts signed out. The user's activity log records it with the user's email and your app's name.
List and revoke connected apps
// Apps with access to your account
const { connected_apps } = await userApi('/me/v1/connected-apps', accessToken)
for (const app of connected_apps) {
console.log(app.name, app.scopes.join(' '), app.current ? '(this app)' : '')
}
// Revoke one
const claude = connected_apps.find((app) => app.name === 'Claude')
await userApi(`/me/v1/connected-apps/${claude.id}`, accessToken, { method: 'DELETE' })
The list holds every app that can still act for the user: those with a remembered consent or a live refresh token. self_registered marks an app that registered itself, such as Claude or ChatGPT, and resource_servers names the servers its tokens are for, such as your MCP server.
Revoking does what revoking from the dashboard does: the app's refresh and access tokens are revoked and the user's consent is forgotten, so connecting it again asks again. A server that introspects tokens refuses the app's next call. A server that checks tokens locally accepts the access token the app holds until it expires, at most access_token_lifetime_seconds from the list response. The user's activity log records the revoke with the user's email and your app's name.
The current session and app
Each list marks one entry current:
- in the sessions, the browser session the calling token was signed in from;
- in the connected apps, the app that is calling.
Both can be ended. Ending the current session signs that browser out, and the calling token keeps working. Revoking the current app revokes the calling token too, so your app has to sign the user in again.
Tokens record the browser session they were signed in from, and refreshed tokens keep it. The ID token carries it as the OIDC sid claim, which is the session's id in the list. Tokens issued before Authalla recorded sessions have no sid, and no session is marked current for them.
Sessions and apps are separate
Ending a browser session never revokes an app, and revoking an app never ends a browser session. A user who signs out of a laptop keeps the agent they connected from it, such as Claude signed in with their own account. To cut everything off, end each session and revoke each app, or have an administrator use Sign out everywhere.