Authagonal

Documentation

Everything you need to get started with Authagonal — from creating your first tenant to wiring SSO, SCIM, and custom branding.

Getting Started

Authagonal gives each tenant a fully standards-compliant OIDC server. Every tenant gets its own issuer URL, discovery document, and token endpoints — no shared infrastructure between tenants. You can go from zero to a working login flow in under 5 minutes.

Create an Account

Sign up at authagonal.io and choose a slug for your account. The slug becomes your issuer domain: {slug}.authagonal.io. After creating your account, verify your email address to get started.

Authagonal signup page showing tenant slug input and email verification

Choose a unique slug for your account during signup

Register a Client

Navigate to Clients in the portal sidebar and click Create. Enter a clientId and clientName for your application. Then configure at least one redirect URI — this is where users are sent after authenticating. For example: https://app.example.com/callback.

Client creation form with clientId, clientName, and redirect URI fields

Register a new OAuth client in the portal

Local Development

Use http://localhost:3000/callback as a redirect URI for local development. Authagonal allows non-HTTPS redirect URIs for localhost origins.

Your First Login

The fastest way to integrate is with oidc-client-ts, a lightweight OIDC client library for JavaScript and TypeScript applications.

oidc-client-ts integration
import { UserManager } from 'oidc-client-ts';

const mgr = new UserManager({
  authority: 'https://acme.authagonal.io',
  client_id: 'my-app',
  redirect_uri: 'https://app.example.com/callback',
  response_type: 'code',
  scope: 'openid profile email',
});

// Redirect to login
mgr.signinRedirect();

// On callback page
const user = await mgr.signinRedirectCallback();
console.log(user.profile); // { sub, email, name, ... }

If you prefer a minimal approach without a library, you can use the standard OAuth 2.0 authorization code flow with plain fetch:

Minimal fetch-based flow
// 1. Redirect the user to the authorization endpoint
const authorizeUrl = new URL('https://acme.authagonal.io/connect/authorize');
authorizeUrl.searchParams.set('client_id', 'my-app');
authorizeUrl.searchParams.set('redirect_uri', 'https://app.example.com/callback');
authorizeUrl.searchParams.set('response_type', 'code');
authorizeUrl.searchParams.set('scope', 'openid profile email');
authorizeUrl.searchParams.set('code_challenge', codeChallenge);
authorizeUrl.searchParams.set('code_challenge_method', 'S256');
window.location.href = authorizeUrl.toString();

// 2. On the callback page, exchange the code for tokens
const res = await fetch('https://acme.authagonal.io/connect/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code: new URLSearchParams(window.location.search).get('code')!,
    redirect_uri: 'https://app.example.com/callback',
    client_id: 'my-app',
    code_verifier: codeVerifier,
  }),
});

const tokens = await res.json();
// tokens.id_token, tokens.access_token, tokens.refresh_token
Authagonal login page with email and password fields, branded with tenant logo

The default login page for your tenant

Sandbox Mode

Test your integration in sandbox mode first. Sandbox tenants use a separate URL ({slug}-sandbox.authagonal.io) and can be refreshed from production at any time without affecting live users.

Dashboard

The portal dashboard gives you a real-time overview of your tenant. It surfaces the metrics that matter most — user growth, authentication activity, and quick navigation to every feature in the portal.

Overview

At the top of the dashboard you will see a welcome message along with your current total user count. Below that, a Daily Active Users chart displays a 7-day history of unique users who authenticated each day, giving you a quick pulse on engagement trends.

Full dashboard view showing welcome banner, user count, and Daily Active Users line chart

The dashboard home screen with DAU chart and activity overview

Activity Metrics

The activity metrics panel displays four stat cards summarizing key authentication events:

  • Successful Logins — total completed authentication flows
  • Failed Logins — incorrect credentials, locked accounts, or policy rejections
  • Active Users — unique users who authenticated in the selected period
  • SCIM Operations — user and group provisioning events from connected IdPs

Use the time range filters to switch between 24 hours, 3 days, 7 days, and 30 days. All stat cards and charts update to reflect the selected window.

Activity metrics panel with four stat cards and a time range filter bar showing 24h, 3d, 7d, and 30d options

Activity metrics with configurable time range

Quick Navigation

Below the metrics panel, navigation cards link directly to every major feature: Clients, Users, Groups, Roles, SSO, SCIM, Branding, and Settings. Each card shows a brief description so new team members can orient themselves quickly.

Clients

OAuth clients represent the applications that authenticate users through your tenant. Each client has its own configuration for redirect URIs, scopes, grant types, token lifetimes, and MFA policy.

Client List

The Clients page displays a table of all registered clients. Each row shows the clientId, display name, allowed grant types as colored badges, and whether PKCE is enabled. Click any row to open the full configuration editor.

Clients table showing clientId, name, grant type badges, and PKCE status for each registered application

Client list with grant type badges and PKCE indicators

Creating a Client

Click Create Client to register a new application. You need to provide two fields:

  • clientId — a unique identifier for the client (e.g. my-spa)
  • clientName — a human-readable display name
Create client form with clientId and clientName input fields

Register a new OAuth client

Deleting a Client

To delete a client, open the client configuration and click the Delete Client button at the bottom of the page. You will be asked to confirm before the client is permanently removed. All active sessions and tokens for the deleted client are immediately invalidated.

Client Configuration Reference

Each client has a comprehensive set of configuration options organized into several sections.

General Settings

SettingDescriptionDefault
clientNameDisplay name shown in consent screens and the portal
requirePkceRequire Proof Key for Code Exchange on authorization code flowsOn
requireClientSecretRequire a client secret for token requests (disable for public clients like SPAs)Off
allowOfflineAccessAllow the client to request refresh tokens via the offline_access scopeOff
alwaysIncludeUserClaimsInIdTokenInclude all user claims directly in the ID token instead of requiring a UserInfo callOff
includeGroupsInTokensInclude the user's group memberships as a groups claim in the ID tokenOff

PKCE Security

Disabling PKCE reduces security for authorization code flows. Only disable this for legacy clients that do not support PKCE. All modern applications should leave PKCE enabled.

URIs

URI fields use a tag input — type a value and press Enter or comma to add it. Click the X on any tag to remove it.

SettingDescription
redirectUrisAllowed callback URLs after authentication. Must exactly match the redirect_uri parameter in authorization requests.
postLogoutRedirectUrisAllowed URLs to redirect to after logout.
allowedCorsOriginsOrigins permitted for cross-origin requests to the token and UserInfo endpoints.
URI configuration section showing tag inputs for redirect URIs, post-logout URIs, and CORS origins

Tag input fields for configuring URIs

Scopes & Grant Types

SettingOptions
allowedScopesopenid profile email offline_access
allowedGrantTypesauthorization_code client_credentials refresh_token device_code

Token Lifetimes

SettingDescriptionDefault
accessTokenLifetimeSecondsHow long access tokens are valid1800 (30 min)
identityTokenLifetimeSecondsHow long ID tokens are valid300 (5 min)
authorizationCodeLifetimeSecondsHow long authorization codes are valid for exchange300 (5 min)
absoluteRefreshTokenLifetimeSecondsMaximum lifetime of a refresh token regardless of activity2592000 (30 days)
slidingRefreshTokenLifetimeSecondsRefresh token expiry resets on each use, up to the absolute lifetime1296000 (15 days)
Token lifetime configuration fields with numeric inputs for each lifetime setting

Configure token lifetimes per client

Logout URIs

Clients can register both back-channel and front-channel logout URIs. Either or both are optional — configure whichever matches how your application clears its session.

SettingDescription
backChannelLogoutUriServer-to-server POST with a signed logout token. Reliable even when the user's browser is offline.
frontChannelLogoutUriRendered in a hidden iframe during logout so the browser clears cookies and local storage.
frontChannelLogoutSessionRequiredWhen on, the logout URL receives iss and sid query parameters so your app can correlate the logout with the specific session.

Use both together

Back-channel guarantees the server is notified; front-channel clears the browser. Most apps benefit from configuring both.

MFA Policy

Each client can override the tenant-wide MFA policy with a per-client setting. The MFA policy dropdown offers three options:

PolicyBehavior
DisabledMFA is never prompted for this client
EnabledUsers can optionally enroll in MFA; they will be prompted if enrolled
RequiredAll users must complete MFA to authenticate through this client
MFA policy dropdown showing Disabled, Enabled, and Required options on the client configuration page

Per-client MFA policy override

Enterprise SSO

Enterprise SSO lets your customers bring their own identity provider. Authagonal supports both SAML 2.0 and OIDC federation with domain-based routing, so users are automatically directed to the correct IdP based on their email address.

SSO domain routing flow — shows how email domains are matched to SAML or OIDC identity providers

Domain-based SSO routing

SAML 2.0 Connections

To create a SAML connection, navigate to the SSO page and select the SAML tab. Provide the following:

FieldDescription
connectionNameA human-readable name for this connection (e.g. "Acme Corp Okta")
entityIdThe SAML Entity ID of the external identity provider
metadataUrlURL to the IdP's SAML metadata XML document

When you save the connection, Authagonal fetches the metadata document and imports the IdP's signing certificate, SSO endpoint URL, and name identifier format. The metadata is periodically refreshed to pick up certificate rotations.

SAML connection creation form with fields for connection name, entity ID, and metadata URL

Create a SAML 2.0 SSO connection

OIDC Connections

To create an OIDC federation connection, select the OIDC tab and provide:

FieldDescription
connectionNameA human-readable name for this connection
discoveryUrlThe OpenID Connect discovery URL (e.g. https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration)
clientIdThe client ID registered with the external IdP for this federation
clientSecretThe client secret for the external IdP registration
OIDC connection creation form with fields for connection name, discovery URL, client ID, and client secret

Create an OIDC federation connection

Domain Routing

Domain routing automatically redirects users to the correct identity provider based on their email domain. When a user enters their email on the login page, Authagonal checks if the domain portion (e.g. acme.com) matches any configured SSO connection. If it does, the user is seamlessly redirected to their organization's IdP.

Email DomainSSO ProviderProtocol
acme.comAcme Corp OktaSAML 2.0
contoso.comContoso Azure ADOIDC
example.orgExample OneLoginSAML 2.0
Domain routing table showing email domains mapped to SSO connections with protocol type

Domain routing maps email domains to identity providers

SP-Initiated Flow

SP-initiated flow is the default — users start at your login page and are routed to the correct IdP automatically. Users can also be deep-linked directly to a specific connection via /saml/{connectionId}/login or /oidc/{connectionId}/login.

JIT Provisioning

By default, when a user signs in via SSO for the first time and doesn't already exist in your tenant, Authagonal creates their account automatically (Just-In-Time provisioning). This can be disabled per connection by checking Disable JIT provisioning when creating or editing the connection.

When JIT provisioning is disabled, only users who have been pre-provisioned — via SCIM, the portal's Users page, or the API — can sign in through that connection. Unknown users receive an access_denied error and are directed to contact their administrator.

Per-Connection Setting

JIT provisioning is controlled per SSO connection, not tenant-wide. You can have one connection that allows JIT (e.g. for a partner org that manages their own users) and another that requires pre-provisioning (e.g. for an enterprise customer using SCIM sync).

Test Before Rollout

Test SSO connections with sandbox mode before rolling out to production users. This lets you verify the IdP configuration, attribute mapping, and domain routing without affecting live authentication flows.

Users

The Users page lets you manage all end users in your tenant. You can search for users, view their details, create new accounts, and see how each user was provisioned.

The search bar supports filtering by email address or user ID. Search is debounced at 300ms so results update as you type without overwhelming the API. Results are paginated at 50 users per page — use the navigation controls at the bottom of the table to move between pages.

User Table

The user table displays the following columns for each user:

ColumnDescription
EmailThe user's email address, shown with a verification badge if the email has been confirmed
User IDThe unique identifier assigned to the user
Full NameFirst and last name combined
StatusActive or Inactive — indicates whether the account is enabled
MFAEnabled or Off — whether multi-factor authentication is enrolled
SourceSCIM or Local — how the user was created
CreatedThe date the user account was created
User list table with columns for email, user ID, name, status, MFA, source, and created date

The user list with search bar and pagination

Creating Users

Click Create User to add a new local user. The form requires:

FieldDescription
emailThe user's email address (must be unique within the tenant)
passwordInitial password (minimum 8 characters, must meet your tenant's password policy)
firstNameThe user's first name
lastNameThe user's last name
Create user form with email, password, first name, and last name fields

Create a new local user

SCIM-Provisioned Users

Users created via SCIM are marked with a "SCIM" badge and cannot have their password changed through the portal. Their lifecycle — creation, updates, and deactivation — is managed entirely by the upstream identity provider.

User Detail

Click any row in the user list to open its detail page. From there you can edit profile data, manage roles, reset MFA, review custom attributes, and delete the user.

Profile

Edit email, first/last name, phone, company, external ID, and toggle the user's active flag. Email changes must remain unique across the tenant; the API returns email_in_use if taken.

Roles

Assign and unassign roles defined on the Roles page. Role membership is surfaced in ID and access tokens when the client has includeRolesInTokens enabled.

Multi-Factor Authentication

See every MFA credential registered for the user — authenticator app (TOTP), WebAuthn/passkeys, and recovery codes — each with its own registered/last-used timestamps. Remove individual credentials, or reset all MFA. Resetting forces the user to re-enroll at next login.

Custom Attributes

Arbitrary key/value data attached to the user. Keys must be unique. Attributes are exposed via the user profile API and SCIM, and can be mapped to access-token claims by configuring a custom scope's userClaims.

Delete User

Permanently removes the user and all their MFA credentials. Type the user's email to confirm — there is no undo.

Groups

Groups let you organize users and include group membership in tokens. Groups can be created manually in the portal or provisioned automatically via SCIM from an external identity provider.

Group List

The Groups page shows all groups in your tenant with the following information:

ColumnDescription
Group NameThe display name of the group
MembersThe number of users currently in the group
SourceSCIM or Manual — how the group was created
CreatedThe date the group was created
Groups list table showing group name, member count, source badge, and created date

Group list with source indicators

Creating a Group

Click Create Group and enter a displayName for the group. Group names should be descriptive and unique within your tenant (e.g. "Engineering", "Billing Admins", "Beta Testers").

Group Detail & Members

Click any group to open the detail view. Here you can see all current members and manage membership:

  • Add members — Enter a user ID to add a user to the group.
  • Remove members — Click the remove button next to any member to remove them individually.
Group detail view showing member list with user IDs and a field to add new members

Manage group membership in the detail view

Groups in Tokens

When includeGroupsInTokens is enabled on a client, the ID token includes a groups claim containing the user's group memberships. Each entry includes the group id and name:

groups claim in ID token
{
  "sub": "user-123",
  "email": "jane@acme.com",
  "groups": [
    { "id": "grp-001", "name": "Engineering" },
    { "id": "grp-002", "name": "Beta Testers" }
  ]
}

Enable per Client

The includeGroupsInTokens setting is configured on each client individually. Navigate to the client's General Settings to enable it.

Roles

Roles support role-based access control (RBAC) in your application. Define roles in Authagonal, assign them to users, and use the roles claim in tokens to enforce authorization in your application logic.

Managing Roles

The Roles page displays a table of all defined roles with inline editing. Each role has:

ColumnDescription
NameA unique identifier for the role (e.g. "admin", "editor", "viewer")
DescriptionA human-readable description of what the role grants
CreatedThe date the role was created

Creating a Role

Click Create Role and provide a name and description. Role names should be concise and follow a consistent naming convention across your application (e.g. lowercase with hyphens: billing-admin).

Inline Editing

Roles support inline editing directly in the table. Click the pencil icon on any role to enter edit mode — the name and description fields become editable. Modify the values, then click the checkmark icon to save. Changes take effect immediately.

Deleting a Role

Click the delete icon on any role to remove it. You will be asked to confirm before the role is permanently deleted. Removing a role does not retroactively invalidate existing tokens — the role will be absent from new tokens issued after deletion.

Roles table with inline editing active, showing editable name and description fields with save and cancel icons

Inline editing of roles in the roles table

Roles in Tokens

Roles assigned to a user are included as a roles claim in the ID token. Your application can read this claim to make authorization decisions:

roles claim in ID token
{
  "sub": "user-123",
  "email": "jane@acme.com",
  "roles": ["admin", "billing-admin"]
}

SCIM Provisioning

SCIM 2.0 (System for Cross-domain Identity Management) enables automatic user and group provisioning from enterprise identity providers like Okta, Azure AD, OneLogin, and JumpCloud. When configured, user accounts and group memberships are automatically synced from the upstream IdP to your Authagonal tenant.

SCIM provisioning flow — shows user lifecycle events flowing from enterprise IdP through Authagonal SCIM API to your app via TCC webhooks

SCIM user lifecycle sync with downstream provisioning

Setup Steps

Follow these steps to enable SCIM provisioning for a client:

  1. Select the client application — Choose the OAuth client that the SCIM provisioning will be associated with.
  2. Generate a SCIM token — Provide a description and an expiry period in days, then generate the token.
  3. Copy the token immediately — The raw token value is displayed only once. Copy it before closing the dialog.
  4. Configure your IdP — In your identity provider's SCIM settings, enter the base URL and bearer token.
  5. Test user sync — Trigger a test sync from your IdP and verify that users appear in the Authagonal portal.

SCIM Base URL

Configure your identity provider with the following base URL:

SCIM Base URL
https://{slug}.authagonal.io/scim/v2

Replace {slug} with your tenant slug.

SCIM configuration page showing client selector, token generation form, and base URL

SCIM setup page with token generation

Token Management

SCIM tokens authenticate provisioning requests from your IdP. You can manage multiple tokens per client:

FieldDescription
DescriptionA label to identify the token (e.g. "Okta Production SCIM")
ExpiryToken lifetime in days (1 to 3650). Leave blank or set a long value for tokens that should not rotate frequently.
StatusActive tokens are in use. Revoked tokens display a Revoked badge and can no longer authenticate requests.

To revoke a token, click the Revoke button next to it. Revoked tokens remain visible in the list for audit purposes but immediately stop accepting requests.

SCIM token list showing active and revoked tokens with description, expiry date, and revoke button

Token management with active and revoked token indicators

Copy Token Immediately

The raw SCIM token is displayed only once when created. Copy it immediately — it cannot be retrieved later. If you lose the token, you will need to generate a new one and update your IdP configuration.

Testing Connectivity

Verify that your SCIM integration is working by querying the ServiceProviderConfig endpoint:

Test SCIM connectivity
curl -H "Authorization: Bearer YOUR_TOKEN" \
  https://acme.authagonal.io/scim/v2/ServiceProviderConfig

A successful response returns a JSON document describing the supported SCIM features, including bulk operations, filtering, and change password capability.

OAuth Scopes

Scopes let clients request specific slices of a user's data or permissions. Authagonal supports both standard OIDC scopes and custom scopes you define for your APIs.

Built-in Scopes

ScopeDescription
openidRequired for any OpenID Connect flow. Issues an ID token.
profileReturns standard profile claims (name, given_name, family_name).
emailReturns the user's email address and verification status.
offline_accessIssues a refresh token alongside the access token.

Custom Scopes

Define your own scopes on the Scopes page. Each scope describes a permission or resource a client can request (for example, billing.read, orders.write).

FieldDescription
nameThe scope identifier sent in token requests (e.g. billing.read).
displayNameHuman-readable label shown on the consent screen.
descriptionLonger explanation shown under the display name on consent.
userClaimsExtra claims added to the access token when this scope is granted.
showInDiscoveryDocumentIf on, the scope appears in /.well-known/openid-configuration.
emphasizeHighlights the scope on the consent screen as sensitive.
requiredPrevents the user from deselecting the scope during consent.

Consent integration

Clients with RequireConsent: true prompt the user on first request. Deleting a scope does not revoke already-issued tokens — revoke them explicitly if needed.

Custom claims on tokens

Custom claims have two halves. The source is per-user data: each AuthUser has a customAttributes dictionary you can populate from the Portal (Users → user → Custom Attributes), via SCIM, or via a TCC provisioning hook. The release is per-scope: each scope's userClaims list names the keys it permits to leave the server.

When a client requests scopes, Authagonal walks the granted scopes, unions their userClaims lists, and emits only those keys from the user's customAttributes. Unknown keys are silently dropped — a client cannot read an attribute by guessing the name. Standard OIDC claims (sub, email, name, etc.) follow the spec and are not subject to the whitelist.

Example: project-scope claims on an access token
# 1. On the user (Portal → Users → {user} → Custom Attributes)
department = "engineering"
employee_id = "E-1042"
seat_tier = "enterprise"

# 2. On a custom scope (Portal → Scopes → projects.read)
name        = "projects.read"
userClaims  = ["department", "seat_tier"]   # <-- whitelist

# 3. Client requests scope=openid projects.read
# Decoded access token (relevant fields only):
{
  "sub": "u-9b…",
  "scope": "openid projects.read",
  "department": "engineering",
  "seat_tier":  "enterprise"
  // employee_id is NOT emitted — it's not in the whitelist for any granted scope.
}

Federation claims override per-session

When a user signs in via an upstream IdP (SAML/OIDC SSO), per-session claims that arrive from the IdP — for example a department attribute mapped from a SAML assertion — flow through the same scope whitelist but win on key collision against the persisted customAttributes. They are emitted on this session's tokens (and survive refresh rotations) without being written back to the user record.

Assigning Scopes to Clients

Add allowed scopes on the Clients → Scopes & Grants tab. A client can only request scopes it has been granted; unknown scopes are rejected with invalid_scope.

Branding

Customize the look and feel of your tenant's login pages. Branding settings let you match the authentication experience to your product's visual identity — from logos and colors to advanced CSS overrides.

Appearance

SettingDescription
appNameThe application name displayed on the login page header and browser tab
logoUrlURL to your logo image. Displayed at the top of the login page. Recommended size: 200x60px or similar aspect ratio.
primaryColorThe primary brand color used for buttons, links, and focus states. Set via a color picker or hex input. A live preview updates as you change the value.
customCssUrlURL to an external CSS file loaded after the default styles. Use this for advanced styling overrides.
Branding appearance settings with app name input, logo URL field, color picker with hex input, and custom CSS URL field

Appearance settings with live color preview

Contact Information

SettingDescription
supportEmailA support email address displayed on login pages. Users see this when they need help with their account.

Login Page Toggles

Control which elements appear on your tenant's login page:

ToggleDescriptionDefault
showForgotPasswordDisplay the "Forgot password?" link on the login formOn
showRegistrationDisplay the "Sign up" link for self-service user registrationOn
showPoweredByDisplay the "Powered by Authagonal" badge at the bottom of the login pageOn
A customized login page showing a branded logo, custom primary color on the sign-in button, and support email in the footer

Example login page with custom branding applied

Custom CSS

For full control over the login page appearance, provide a CSS file URL in your branding settings. The file is loaded after the default styles, so your rules take precedence.

CSS Custom Properties

The login page supports CSS custom properties (variables) for common overrides. Set these in your CSS file to change colors, fonts, and shape without writing complex selectors.
/* your-custom-styles.css */
:root {
--auth-bg: #1a1a2e;
--auth-card-bg: #16213e;
--auth-heading: #e0e0e0;
--auth-radius: 12px;
--auth-font: 'Inter', sans-serif;
}
VariableDescriptionDefault
--auth-bgPage background color#f3f4f6
--auth-card-bgLogin card backgroundwhite
--auth-headingHeading text color#111827
--auth-radiusCard border radius0.5rem
--auth-fontFont familyinherit

Dark Mode

The login app ships with light, dark, and system themes. Users pick from a toggle on the login page; the choice persists across sessions. When set to system, the SPA tracks prefers-color-scheme live.

Light values are declared at :root; dark overrides are scoped to .dark. Tenant branding set via customCssUrl always wins — so your colors persist regardless of the user's theme.

Element Selectors

For more granular control, target specific elements using data-auth attributes. These selectors are stable across updates — they won't break when we change internal class names.
SelectorElement
[data-auth="page"]Full-page background container
[data-auth="header"]Logo and app name area
[data-auth="logo"]Logo image
[data-auth="app-name"]App name heading (when no logo is set)
[data-auth="content"]Main content area (forms, messages)
[data-auth="login-form"]Login form element
[data-auth="email-field"]Email input wrapper
[data-auth="password-field"]Password input wrapper
[data-auth="submit-button"]Sign-in button
[data-auth="languages"]Language selector bar

Settings

Configure tenant-wide security policies, webhooks, and environment settings. These settings apply globally across all clients unless overridden at the client level.

Password Policy

Define the password complexity requirements for all users in your tenant:

SettingRangeDefault
minPasswordLength6 – 1288
requireUppercaseOn / OffOn
requireLowercaseOn / OffOn
requireDigitOn / OffOn
requireSpecialCharOn / OffOn
Password policy settings showing minimum length slider and toggle switches for character requirements

Password policy configuration

MFA Policy

The tenant-wide MFA policy sets the default multi-factor authentication behavior. Individual clients can override this setting.

PolicyBehavior
DisabledMFA is not available. Users cannot enroll in MFA.
EnabledMFA is optional. Users can choose to enroll and will be prompted at login if enrolled.
RequiredMFA is mandatory. All users must enroll in MFA and complete a second factor at every login.

Session & Lockout

Control session duration and account lockout behavior:

SettingRangeDefault
sessionLifetimeMinutes5 – 43,200 (30 days)60
maxFailedAttempts1 – 1005
lockoutDurationMinutes1 – 1,440 (24 hours)10
Session and lockout settings with numeric inputs for session lifetime, max failed attempts, and lockout duration

Session and lockout configuration

Webhooks

Webhooks let you react to authentication events in real time. Two events (onUserAuthenticated, onTokenIssued) are enforceable — by default they fire asynchronously and don't block the user, but you can opt in to enforcement per event so a non-2xx response or {"allow": false} body rejects the action. The remaining events are notifications — always fire-and-forget, never block.

EventTypeDescription
onUserAuthenticatedEnforceableFired after a successful login. Defaults to fire-and-forget so login latency is unaffected. Toggle <code>webhookEnforceUserAuthenticated</code> to make it blocking — a non-2xx response or <code>{"allow": false}</code> body then rejects the login.
onTokenIssuedEnforceableFired before tokens are minted (authorization_code, refresh_token, client_credentials). Defaults to fire-and-forget. Toggle <code>webhookEnforceTokenIssued</code> to make it blocking — a non-2xx response or <code>{"allow": false}</code> body then prevents token issuance.
onUserCreatedNotificationFire-and-forget notification when a new user registers or is provisioned via SCIM.
onUserUpdatedNotificationFire-and-forget notification when a user record is updated (profile changes, role changes, SCIM updates).
onUserDeletedNotificationFire-and-forget notification when a user is deleted, either via Portal/SCIM or by retention policy.
onLoginFailedNotificationFire-and-forget notification when a login attempt fails due to bad credentials, lockout, or policy rejection.

Additional webhook settings:

SettingRangeDefaultDescription
webhookTimeoutSeconds1 – 305Maximum time to wait for an enforcement webhook response before timing out
webhookFailOpenOn / OffOnWhen enabled, if an enforcement webhook is unreachable or times out, the operation is allowed to proceed
Webhook configuration section showing URL inputs for each event type, timeout slider, and fail-open toggle

Webhook event configuration

Enforcement Webhook Availability

Enforcement webhooks can block authentication flows. If your webhook endpoint goes down and webhookFailOpen is disabled, no users will be able to log in. Use fail-open mode unless you have strict compliance requirements that mandate blocking on webhook failure.

Verifying webhooks

Once any webhook URL is configured, Authagonal mints a per-tenant signing secret (a whsec_… value shown read-only under Settings → Webhooks). Every outbound delivery carries an X-Authagonal-Signature: t=<unix>,v1=<hex> header, where v1 is HMAC-SHA256(secret, "{t}.{body}") computed over the raw request body. Recompute it on your endpoint and constant-time-compare to confirm the request genuinely came from Authagonal and wasn't tampered with — and reject deliveries whose t is too old to block replays.

Verify a webhook (Node.js)
import crypto from 'node:crypto';

// rawBody MUST be the exact bytes Authagonal sent — verify before any JSON re-serialization.
function verifyAuthagonalWebhook(signatureHeader, rawBody, signingSecret) {
  const parts = Object.fromEntries(signatureHeader.split(',').map((p) => p.split('=')));
  const { t, v1 } = parts;

  // Replay protection: reject deliveries older than 5 minutes.
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;

  const expected = crypto
    .createHmac('sha256', signingSecret)
    .update(`${t}.${rawBody}`)
    .digest('hex');

  return v1.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}

Rotating the signing secret

Use Regenerate next to the signing secret to rotate it — for example after a suspected leak. The previous secret is invalidated immediately, so update your verifier with the new value or in-flight deliveries will start failing their signature check.

Maintenance Window

Set a preferred maintenance window for disruptive operations such as certificate rotations and infrastructure updates. Choose a UTC hour (0–23) — the portal also displays the equivalent time in your local timezone for convenience.

Sandbox Environment

The sandbox environment is a full clone of your production tenant, available at a separate URL. Use it to test configuration changes, SSO integrations, and webhook endpoints without affecting live users.

ActionDescription
Enable SandboxCreates a sandbox copy of your production tenant. The sandbox URL is your tenant slug with a -sandbox suffix.
Refresh from LiveSyncs the sandbox environment with the current production configuration and user data.
Disable SandboxPermanently deletes the sandbox environment and all its data.

The sandbox is accessible at {slug}-sandbox.authagonal.io.

Sandbox settings showing enable/disable toggle, refresh from live button, and sandbox URL display

Sandbox environment controls

Billing

Manage your subscription and billing through the portal's Billing page. This page gives you an overview of your current plan and provides access to the Stripe billing portal for managing payment methods, invoices, and plan changes.

Subscription Information

The billing page displays your current subscription details at a glance. You'll see a status badge indicating your subscription state — active, trialing, past_due, canceled, or unpaid — along with your plan name, the current billing period (start and end dates), and whether your subscription is set to cancel at the end of the current period.

Manage Subscription

Click the Manage Subscription button to open the Stripe billing portal in a new window. From there you can update your payment methods, view and download invoices, change your plan, or cancel your subscription.

If no subscription exists yet, a Setup Billing call-to-action is shown instead, which guides you through selecting a plan and entering payment details.

Billing page showing subscription status, plan name, billing period, and Manage Subscription button

The billing page displays your current subscription details and provides access to Stripe

Payment Security

All billing is handled through Stripe. Your payment information is never stored on Authagonal servers.

Custom Domains

Serve your authentication pages from your own domain (e.g., auth.yourdomain.com) instead of the default {slug}.authagonal.io. Custom domains give your users a seamless, branded authentication experience.

Adding a Domain

Enter the hostname you want to use in the add domain form (e.g., auth.yourdomain.com). Once added, the domain will appear in your domain list with a pending_verification status.

DNS Verification

Create a CNAME record pointing your domain to {slug}.authagonal.io. Once the DNS record is in place, click Verify to check DNS propagation.

CNAME record
auth.yourdomain.com.  CNAME  acme.authagonal.io.

DNS Propagation

DNS propagation can take up to 48 hours. If verification fails, wait and try again.

TLS Certificates

Once your domain is verified, you need a TLS certificate so users can connect securely over HTTPS. Authagonal supports two options:

Automatic (cert-manager) — Authagonal provisions and renews TLS certificates automatically using cert-manager. This is the recommended option for most users. No additional configuration is required.

Bring Your Own (BYO) — Upload your own certificate and private key in PEM format. This option is useful if your organization requires certificates from a specific certificate authority. Certificate expiry is tracked so you can renew before it lapses.

Domain Status

Each domain displays a status badge indicating its current state: pending_verification (DNS not yet confirmed), verified (DNS confirmed, TLS pending), active (fully operational), or failed (configuration issue detected).

Domain list showing domains with status badges and verification controls

The domain list displays each custom domain and its current status

BYO certificate upload form with certificate and private key PEM fields

Upload your own TLS certificate and private key in PEM format

BYO Certificate Renewal

Keep your BYO certificate renewed. Expired certificates will cause browser security warnings for your users.

Email Configuration

Configure how your tenant sends transactional emails — verification, password reset, and MFA notifications. Choose between the default shared sender, a verified custom domain via Resend, or your own SMTP server.

Email Providers

ProviderDescriptionSetup
DefaultEmails sent from noreply@authagonal.io using our shared Resend infrastructure.No configuration needed — works out of the box.
Resend Custom DomainEmails sent from your own verified domain via Resend.Register your domain, add DNS records, verify ownership.
Custom SMTPEmails sent through your own SMTP server.Provide SMTP host, port, credentials, and TLS settings.

Sender Identity

Sender email and name are shared across all provider modes. Sender email is required; sender name falls back to the tenant name when empty.

FieldDescription
senderEmailThe From address on outbound emails. Must be on a verified domain for Resend Custom Domain mode.
senderNameDisplay name shown in the recipient's inbox.

Resend Custom Domain

Verify your sending domain with Resend once, then use it as the From address for this tenant. DNS TXT records (SPF, DKIM) are provided by the Domains page; Resend validates them automatically.

Custom SMTP

Bring your own SMTP server — useful for internal relays, vendors not covered by Resend, or regulatory pinning.

FieldDescription
hostSMTP server hostname (e.g. smtp.example.com).
portConnection port. 587 for STARTTLS, 465 for implicit TLS, 25 for unauthenticated internal relays.
usernameAuth username (optional — leave blank for unauthenticated relays).
passwordAuth password. Stored encrypted in the tenant settings secret.
useTlsRequire TLS. Leave on unless you are targeting a trusted internal relay.

Custom Sending Domain

When using the Resend provider, you can register your own domain so emails come from your brand (e.g. noreply@acme.com) instead of @authagonal.io.

  1. Go to Settings → Email and select the Resend Custom Domain provider.
  2. Enter your domain name and click Register.
  3. Add the DNS records shown (DKIM, SPF, and return path) to your domain's DNS.
  4. Click Check Verification — once DNS propagates (usually 1–10 minutes), the domain status will change to verified.

DNS Propagation

DNS changes can take up to 48 hours to propagate globally, though most providers update within minutes. You can check verification as many times as needed.

Testing

Use the Send Test Email button in Settings → Email to verify your configuration. A test email will be sent to your admin email address using the currently saved settings.

Audit Log

The Audit Log provides a read-only record of all administrative actions performed on your tenant. Every change made through the portal or API is captured with full context, giving you a complete trail for compliance and troubleshooting.

Log Columns

ColumnDescription
TimestampThe date and time the action occurred
ActorThe email address of the admin who performed the action, or "system" for automated actions
ActionThe type of action performed (e.g., Client Created, Settings Updated)
EntityThe target of the action in type:id format (e.g., client:my-app)
DetailAdditional context about the change

Tracked Actions

The following administrative actions are recorded in the audit log:

CategoryActions
ClientsClient Created, Client Updated, Client Deleted
SSO ConnectionsSAML Connection Created, SAML Connection Deleted, OIDC Connection Created, OIDC Connection Deleted
UsersUser Created, User Updated
SettingsSettings Updated, Branding Updated
DomainsDomain Added, Domain Verified, Domain Deleted
SCIMSCIM Token Created, SCIM Token Revoked
RolesRole Created, Role Updated, Role Deleted
GroupsGroup Created, Group Deleted
TeamTeam Member Invited, Team Member Removed
Audit log table showing timestamped administrative actions with actor, action, entity, and detail columns

The audit log provides a complete record of all administrative actions

Retention

Audit logs are retained for the lifetime of your tenant and cannot be modified or deleted.

Backups

Authagonal automatically backs up your tenant data on an hourly schedule. Backups include all users, groups, roles, clients, SSO connections, SCIM tokens, branding, and settings. You can view backup history and download the latest complete backup from the Backups page.

Backups page showing backup history with timestamps, types, and entity counts

How Backups Work

  • A full backup runs once daily, capturing every table in your tenant's storage shard.
  • Incremental backups run hourly, capturing only rows that changed since the last backup.
  • Backups are stored in Azure Blob Storage with the same managed identity used by your tenant.
  • Deleted records are tracked via tombstones and included in backups for audit completeness.

Downloading Backups

Click Download Latest to get a ZIP file containing the most recent full backup merged with all subsequent incremental backups. Each table is exported as a JSONL file (one JSON object per line).

Backup Format

Backups are exported as JSONL (JSON Lines) — one entity per line per table. This format is easy to parse, diff, and import into other systems.

Provisioning Apps

Provisioning apps receive real-time webhook notifications when users are created or authenticated in your tenant. This enables downstream systems to automatically set up accounts, assign licenses, or sync user data without manual intervention.

How It Works

When a user event occurs (creation or authentication), Authagonal calls your provisioning app's callback URL using the TCC (Try/Confirm/Cancel) pattern. This three-phase approach ensures reliable provisioning across multiple downstream systems:

PhaseEndpointPurpose
/tryPOST {callbackUrl}/tryChecks if the app can handle the user. Return 200 to accept or 4xx to reject.
/confirmPOST {callbackUrl}/confirmCommits the operation after all apps have accepted the /try phase.
/cancelPOST {callbackUrl}/cancelRolls back the operation if another app fails during the /try phase.

Webhook Payload

Each webhook request includes a JSON payload with the following fields:

FieldTypeDescription
eventstringThe event type (e.g., user.created, user.authenticated)
userIdstringThe unique identifier of the user
emailstringThe user's email address
namestringThe user's display name
tenantIdstringYour tenant identifier
timestampstringISO 8601 timestamp of the event

Adding a Provisioning App

To add a provisioning app, provide a name, a callback URL, and an optional API key. The API key is sent as a Bearer token in the Authorization header of each webhook request, allowing your app to authenticate requests from Authagonal.

Testing

Click Test next to any provisioning app to send a test request to your callback URL. The test results display the HTTP status code and response body, helping you verify that your app is receiving and processing webhooks correctly.

Provisioning apps page showing configured apps with test results displaying HTTP status and response body

Test provisioning apps to verify webhook delivery and response handling

Plan Limits

The maximum number of provisioning apps is configurable per tenant, with a default limit of 6. This limit can be adjusted by an admin if your workflow requires additional provisioning targets.

API Key Authentication

If an API key is set, it's sent as a Bearer token in the Authorization header. Use this to authenticate webhook requests from Authagonal.

Team

The Team page manages portal administrators — the people who can access and configure your tenant through the management portal. All team members have full administrative access to every aspect of your tenant configuration.

Admin List

The admin list displays each team member's name, email address, and the date they were added. A "You" indicator is shown next to the current user's row so you can easily identify your own account.

Inviting Admins

To invite a new team member, provide their email address, name, and a temporary password (minimum 8 characters). The invited user signs in with the temporary password and should change it on first login.

Invite Fields

Admin invitations create a fully provisioned user — no email round-trip required.

FieldDescription
emailEmail address of the new admin. Must be unique in the tenant.
nameDisplay name shown in the admin list.
tempPasswordTemporary password the invitee uses on first login. They'll be prompted to change it. Leave blank to auto-generate and send via email.

Removing Admins

Click Remove next to any team member to revoke their access. A confirmation dialog is shown before the removal is finalized. You cannot remove yourself — there must always be at least one admin on the team.

Team page showing the admin list with name, email, date added columns, a You indicator, and invite/remove controls

Manage portal administrators from the Team page

No Owner Role

There is no "owner" role distinction. All portal admins have full access to tenant configuration. Be careful who you invite.

Import & migrate

Migrate an existing identity system into your Authagonal tenant. Two sources are supported — Duende IdentityServer (a SQL Server database) and Auth0 (the Management API). Each runs a read-only preview so you can review exactly what will be copied before committing.

Import from Duende IdentityServer

Migrate clients, scopes, users, and roles from an existing Duende IdentityServer SQL Server database into your Authagonal tenant. The import runs in two phases — preview and commit — so you can review what will be copied before any changes are made.

What Gets Imported

The importer reads from Duende's ConfigurationDb and ASP.NET Identity tables and writes mapped rows into your tenant. Short-lived artifacts like persisted grants, device codes, and signing keys are skipped.

EntitySource TablesNotes
ClientsClients, ClientSecrets, ClientGrantTypes, ClientScopes, ClientRedirectUrisDisabled clients are imported disabled. Expired secrets are skipped.
ScopesApiScopes, ApiResources, IdentityResourcesUser-claim mappings are preserved where recognised.
UsersAspNetUsers, AspNetUserClaimsPassword hashes (ASP.NET Identity V3) copy verbatim and rehash on first login.
RolesAspNetRoles, AspNetUserRolesRole assignments are preserved.
External LoginsAspNetUserLoginsStored for reference; reconnect upstream IdPs via SSO after import.

Preview Before Commit

Paste your Duende ConfigurationDb / IdentityDb connection string and click Run preview. The preview opens a read-only connection and counts every row that would be imported — no writes occur.

  • Entity counts for clients, scopes, users, roles, and role assignments.
  • Owner collisions — tenant admins whose existing <code>userId</code> differs from the incoming Duende <code>sub</code>.
  • Warnings for unknown tables and unmapped columns so you know what will be dropped.
Import preview panel showing entity counts, owner collisions, and warnings before committing the import

Preview panel with counts, collisions, and warnings

Password Hashes

Duende stores passwords using ASP.NET Identity V3 (PBKDF2). Authagonal's PasswordHasher verifies that format directly and rehashes to the native format on the first successful sign-in — users keep their existing passwords without a reset flow.

Owner UserId Rotation

If a tenant admin's email matches a user in the Duende database, the admin's Authagonal userId is rotated to match the Duende sub. This keeps tokens and audit entries consistent after cutover. The preview lists every collision before you commit.

You may be signed out

If your own account is among the rotated admins, the portal signs you out immediately after the import so your next session carries the new userId. Sign back in with the same credentials.

Running the Import

Click Start import after reviewing the preview. The commit phase writes clients, scopes, users, roles, and external-login references into your tenant stores. Duplicate clientId, scope name, email, and role name rows are skipped — the importer is safe to re-run.

What's Not Imported

  • Persisted grants, device codes, server-side sessions — short-lived, regenerated automatically.
  • Signing keys — Authagonal issues its own per-tenant keys.
  • Custom columns and tables — anything outside Duende's standard schema is surfaced as a warning so you know the data was dropped.
  • Disabled clients — imported in a disabled state; re-enable them from the Clients page when ready.

Not available in sandbox

Import runs against the live tenant only. Switch out of sandbox mode before importing.

Import from Auth0

Connect Authagonal to your Auth0 tenant’s Management API and bring your applications, APIs, roles, users, and enterprise connections across. Imported user and application IDs are preserved, so existing sub and client_id references keep resolving after cutover.

What you’ll need

Create a Machine-to-Machine application in Auth0 authorized for the Management API, granted these read scopes: read:users, read:clients, read:resource_servers, read:roles, read:connections, read:client_grants. Paste its domain, client ID, and client secret into the import form — they’re used only for the import.

What Gets Imported

EntitySource TablesNotes
Applicationsclients, client-grantsPublic vs confidential is detected automatically. Client secrets are re-hashed so they keep working.
APIs & scopesresource-serversAudiences and scopes are assigned to each client from its grants.
Rolesroles + assignmentsPer-user role assignments are preserved.
Usersusers + identitiesProfiles and metadata transfer; social/enterprise identities become linked logins.
Connectionsconnections (OIDC)Enterprise OIDC connections become federated providers. SAML, social, and database connections are skipped with a warning.

Passwords

Auth0’s Management API never returns password hashes. If you have Auth0’s support-assisted bulk password export (NDJSON), provide it — bcrypt hashes import verbatim and your users keep their passwords with no reset. That file also carries your full user set, lifting Auth0’s 1,000-user API listing limit. Without it, users import as profiles and set a new password on first sign-in.

Same preview, rotation, and limits

The preview, owner-userId rotation, re-runnable commit, and sandbox restriction described above apply to Auth0 imports too.

API Reference

Each tenant exposes a standards-compliant OIDC server at https://{slug}.authagonal.io. All endpoints follow the OAuth 2.0 and OpenID Connect specifications. This reference covers every endpoint your application may need to interact with.

OIDC Authorization Code Flow with PKCE — sequence diagram showing the 9-step flow between your app, the browser, and Authagonal

Authorization Code Flow with PKCE

OIDC Discovery & JWKS

The discovery document lets OIDC client libraries automatically configure themselves. No authentication is required for either endpoint.

GET /.well-known/openid-configuration

Returns the OpenID Provider Configuration document. The response includes all metadata your client needs to interact with this tenant.

FieldDescription
issuerThe tenant issuer URL
authorization_endpointURL for authorization requests
token_endpointURL for token exchange
userinfo_endpointURL for fetching user claims
jwks_uriURL for the JSON Web Key Set
revocation_endpointURL for token revocation
introspection_endpointURL for token introspection
end_session_endpointURL for logout / end-session
device_authorization_endpointURL for device authorization requests
pushed_authorization_request_endpointURL of the Pushed Authorization Request endpoint (RFC 9126).
require_pushed_authorization_requestsWhether the tenant globally requires PAR. Even when this is <code>false</code>, individual clients can still set <code>RequirePushedAuthorizationRequests = true</code>.
scopes_supportedList of supported scopes
response_types_supportedSupported response types
grant_types_supportedSupported grant types
code_challenge_methods_supportedSupported PKCE methods (S256)
backchannel_logout_supportedWhether back-channel logout is supported

GET /.well-known/openid-configuration/jwks

Returns the JSON Web Key Set used for verifying token signatures. The response contains a keys array with RSA public keys, each including kty, use, kid, alg, n, and e fields.

Fetch discovery document
curl https://acme.authagonal.io/.well-known/openid-configuration

Authorization Endpoint

GET /connect/authorize

Initiates an authorization code flow. The user must have an active session or they will be redirected to the login page. On success, the user is redirected back to your application with an authorization code.

ParameterRequiredDescription
response_typeYesMust be "code"
client_idYesYour registered client identifier
redirect_uriYesMust exactly match a registered redirect URI
scopeYesSpace-separated list of scopes (e.g. "openid profile email")
stateRecommendedOpaque value for CSRF protection, returned unchanged in the redirect
code_challengeRequired if PKCEBase64url-encoded SHA-256 hash of the code_verifier
code_challenge_methodRequired if PKCEMust be "S256"
nonceOptionalValue bound to the ID token for replay protection
login_hintOptionalPre-fill the email field on the login page

Success response: 302 redirect to redirect_uri with code and state query parameters.

Error response: 302 redirect with error, error_description, and state query parameters.

PKCE Required

PKCE is required by default for all clients. Generate a code_verifier (a random string of 43 or more characters), hash it with SHA-256, and base64url-encode the result to create the code_challenge.

Pushed Authorization Requests (PAR)

RFC 9126. Instead of putting every authorize parameter on the URL, your client POSTs them to /connect/par with normal client authentication and gets back a short-lived opaque request_uri. The browser then visits /connect/authorize?client_id=...&request_uri=... — nothing else lands in browser history, server logs, or Referer headers, and the server has already integrity-checked the parameters under client auth.

POST /connect/par

Client authentication is the same as /connect/token: HTTP Basic with client_id/client_secret, or form-encoded credentials. Public clients post without a secret. The body carries the same parameters you would normally send to /connect/authorize; request_uri itself is rejected (chaining a PAR is forbidden by §2.1 of the spec). Returns 201 Created.

ParameterRequiredDescription
client_idYesYour client ID. Must match the authenticated client.
client_secretConfidential clientsYour client secret. Required for confidential clients.
response_typeYesMust be "code"
redirect_uriYesMust exactly match a registered redirect URI
scopeYesSpace-separated list of scopes (e.g. "openid profile email")
code_challengeRequired if PKCEBase64url-encoded SHA-256 hash of the code_verifier
code_challenge_methodRequired if PKCEMust be "S256"
stateRecommendedOpaque value for CSRF protection, returned unchanged in the redirect
nonceOptionalValue bound to the ID token for replay protection

Response

FieldDescription
request_uriSingle-use opaque reference, e.g. <code>urn:ietf:params:oauth:request_uri:abc123…</code>. Pass it to <code>/connect/authorize</code> as <code>request_uri</code>.
expires_inLifetime of the <code>request_uri</code> in seconds. Default is 90 — typical reference-IdP value.

On the follow-up GET /connect/authorize?client_id=…&request_uri=…, all other parameters are pulled from the pushed payload and any extra query parameters are ignored. The client_id on the authorize call must match the client that pushed the request. Once consumed (or once expires_in elapses), the request_uri is removed from the store.

Forcing PAR per client

Toggle Require PAR on a client (Portal → Clients → client → Advanced) to refuse plain /connect/authorize calls from it. The recommended posture for high-risk clients combines RequirePushedAuthorizationRequests = true with PKCE — that removes the URL bar as an attack surface entirely.
Push an authorization request and follow up
# 1. Push parameters (server returns request_uri + expires_in)
curl -X POST https://acme.authagonal.io/connect/par \
  -u "my-app:CLIENT_SECRET" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "response_type=code" \
  -d "redirect_uri=https://app.example.com/callback" \
  -d "scope=openid profile email" \
  -d "state=$(openssl rand -hex 16)" \
  -d "code_challenge=YOUR_CODE_CHALLENGE" \
  -d "code_challenge_method=S256"

# 2. Send the user to /authorize with only client_id + request_uri
# https://acme.authagonal.io/connect/authorize?client_id=my-app&request_uri=urn:ietf:params:oauth:request_uri:abc123...

Token Endpoint

POST /connect/token

Exchanges credentials for tokens. Requests must use Content-Type: application/x-www-form-urlencoded. Client authentication can be provided via HTTP Basic auth (Authorization: Basic base64(client_id:client_secret)) or as form body parameters (client_id + client_secret).

Authorization Code Grant

ParameterRequiredDescription
grant_typeYes"authorization_code"
codeYesThe authorization code from the redirect
redirect_uriYesMust match the URI used in the authorization request
code_verifierRequired if PKCEThe original random string used to generate the code_challenge
client_idYesYour client identifier (if not using Basic auth)
client_secretConfidential clientsYour client secret (if not using Basic auth)

Refresh Token Grant

ParameterRequiredDescription
grant_typeYes"refresh_token"
refresh_tokenYesThe refresh token to exchange
client_idYesYour client identifier
client_secretConfidential clientsYour client secret

Client Credentials Grant

ParameterRequiredDescription
grant_typeYes"client_credentials"
client_idYesYour client identifier
client_secretYesYour client secret
scopeOptionalSpace-separated scopes to request

Device Code Grant

ParameterRequiredDescription
grant_typeYes"urn:ietf:params:oauth:grant-type:device_code"
device_codeYesThe device code from the device authorization response
client_idYesYour client identifier
client_secretConfidential clientsYour client secret

Token response:

FieldDescription
access_tokenThe access token for API calls
token_type"Bearer"
expires_inToken lifetime in seconds
id_tokenOpenID Connect ID token (when openid scope is requested)
refresh_tokenRefresh token (when offline_access scope is granted)
Exchange authorization code with PKCE
curl -X POST https://acme.authagonal.io/connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=AUTHORIZATION_CODE" \
  -d "redirect_uri=https://app.example.com/callback" \
  -d "client_id=my-app" \
  -d "code_verifier=YOUR_CODE_VERIFIER"

UserInfo Endpoint

GET /connect/userinfo

Returns claims about the authenticated user. Requires a valid access token with the openid scope.

FieldTypeDescription
substringUnique user identifier
emailstringUser email address
email_verifiedbooleanWhether the email has been verified
given_namestringFirst name
family_namestringLast name
namestringFull display name
phone_numberstringPhone number (if provided)
org_idstringOrganization identifier
rolesstring[]Array of assigned roles
groupsobject[]Array of group memberships, each with id and name
Fetch user info
curl https://acme.authagonal.io/connect/userinfo \
  -H "Authorization: Bearer ACCESS_TOKEN"

Token Introspection (RFC 7662)

POST /connect/introspect

Validates a token and returns its metadata. Requires client credentials (Basic auth or form body parameters).

ParameterRequiredDescription
tokenYesThe token to introspect
token_type_hintOptionalHint about the token type (e.g. "refresh_token")

Active token response:

FieldDescription
activetrue
subSubject (user ID)
client_idClient that the token was issued to
scopeSpace-separated scopes granted
issIssuer
expExpiration time (Unix timestamp)
iatIssued-at time (Unix timestamp)
audAudience
token_typeToken type (e.g. "Bearer")

Inactive token response: { "active": false }

Always 200 OK

Per RFC 7662, the introspection endpoint always returns 200 OK — never 401 or 403. This prevents token enumeration attacks. An invalid or expired token simply returns active: false.
Introspect a token
curl -X POST https://acme.authagonal.io/connect/introspect \
  -u "my-app:CLIENT_SECRET" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "token=ACCESS_OR_REFRESH_TOKEN"

Token Revocation (RFC 7009)

POST /connect/revocation

Revokes a previously issued token. Requires client credentials.

ParameterRequiredDescription
tokenYesThe token to revoke
token_type_hintOptionalHint about the token type (e.g. "refresh_token")

The endpoint always returns 200 OK, even for invalid or already-revoked tokens, per the RFC 7009 specification.

Refresh Tokens Only

Currently supports refresh token revocation. Access tokens are stateless JWTs and cannot be revoked — they remain valid until they expire naturally.

Device Authorization (RFC 8628)

POST /connect/deviceauthorization

Initiates the device authorization flow for input-constrained devices (CLIs, smart TVs, IoT devices). The device displays a code to the user, who then approves the request on a separate device with a browser.

ParameterRequiredDescription
client_idYesYour client identifier
client_secretConfidential clientsYour client secret
scopeOptionalSpace-separated scopes (defaults to "openid")

Response:

FieldDescription
device_codeDevice verification code (used for polling)
user_codeUser-facing code in XXXX-XXXX format
verification_uriURL the user visits to enter the code
verification_uri_completeURL with the user_code pre-filled
expires_in600 (seconds — the code is valid for 10 minutes)
interval5 (seconds — minimum polling interval)

Approval flow: The user visits the verification_uri, enters the user_code, and approves the request. Meanwhile, the device polls the token endpoint with the device_code.

Polling error codes:

ErrorMeaning
authorization_pendingUser has not yet approved — keep polling
expired_tokenThe device code has expired — restart the flow
access_deniedThe user denied the authorization request
Request device authorization
curl -X POST https://acme.authagonal.io/connect/deviceauthorization \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=my-cli" \
  -d "scope=openid profile email"

End Session / Logout

GET POST /connect/endsession

Signs out the current user session, triggers back-channel logout to all clients with a registered BackChannelLogoutUri, and revokes all grants.

ParameterRequiredDescription
id_token_hintOptionalThe ID token — used to validate the post_logout_redirect_uri
post_logout_redirect_uriOptionalWhere to redirect after logout (must be registered)
stateOptionalOpaque value returned in the redirect

If a valid post_logout_redirect_uri is provided and matches a registered URI, the user receives a 302 redirect. Otherwise, a JSON response confirms the session has been ended.

Back-Channel Logout

When a user signs out, Authagonal sends a signed JWT to each client's BackChannelLogoutUri. The JWT contains sub, aud, iss, and the http://schemas.openid.net/event/backchannel-logout event claim. Your application should invalidate the user's local session when it receives this notification.

SCIM 2.0 API Reference

Authagonal supports the SCIM 2.0 protocol for automated user and group provisioning. Identity providers such as Okta, Azure AD, and OneLogin can use this API to keep your Authagonal tenant in sync with your corporate directory.

Base URL: https://{slug}.authagonal.io/scim/v2

Authentication: All requests require a Bearer token. Generate a SCIM token in the portal under Settings > SCIM Provisioning.

Common headers:

HeaderValue
AuthorizationBearer SCIM_TOKEN
Content-Typeapplication/scim+json

List endpoints support pagination via startIndex (1-based) and count (max 200) query parameters, and filtering via the filter parameter (e.g. userName eq "user@example.com").

Users

GET /scim/v2/Users — List users with optional pagination and filtering.

Query ParameterDescription
startIndex1-based index of the first result (default: 1)
countMaximum number of results per page (max: 200)
filterSCIM filter expression (e.g. userName eq "user@example.com")

GET /scim/v2/Users/{id} — Get a single user by their Authagonal user ID.

POST /scim/v2/Users — Create a new user. Returns 201 Created.

FieldRequiredDescription
userNameYesEmail address (must be unique within the tenant)
name.givenNameNoFirst name
name.familyNameNoLast name
displayNameNoFull display name
activeNoWhether the user is active (default: true)
externalIdNoIdentifier from the upstream identity provider

PUT /scim/v2/Users/{id} — Full replacement of a user resource. All fields must be provided.

PATCH /scim/v2/Users/{id} — Partial update using SCIM PatchOp.

OperationSupported PathsExample Value
replaceactive, name.givenName, name.familyName, externalIdtrue / false, or a string value
addname.givenName, name.familyName, externalIdA string value
removeexternalId(no value needed)

DELETE /scim/v2/Users/{id} — Soft deletes the user (deactivates the account and revokes all tokens). Returns 204 No Content.

Create a user via SCIM
curl -X POST https://acme.authagonal.io/scim/v2/Users \
  -H "Authorization: Bearer SCIM_TOKEN" \
  -H "Content-Type: application/scim+json" \
  -d '{
    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
    "userName": "jane@acme.com",
    "name": {
      "givenName": "Jane",
      "familyName": "Smith"
    },
    "displayName": "Jane Smith",
    "active": true,
    "externalId": "ext-12345"
  }'

Groups

GET /scim/v2/Groups — List all groups with optional pagination and filtering.

GET /scim/v2/Groups/{id} — Get a single group by ID, including its members list.

POST /scim/v2/Groups — Create a new group. Returns 201 Created.

FieldRequiredDescription
displayNameYesGroup display name
membersNoArray of member objects, each with a value field containing the user ID
externalIdNoIdentifier from the upstream identity provider

PUT /scim/v2/Groups/{id} — Full replacement of a group resource (including its member list).

PATCH /scim/v2/Groups/{id} — Partial update for adding or removing group members.

DELETE /scim/v2/Groups/{id} — Hard deletes the group. Returns 204 No Content.

Add members to a group via PATCH
curl -X PATCH https://acme.authagonal.io/scim/v2/Groups/GROUP_ID \
  -H "Authorization: Bearer SCIM_TOKEN" \
  -H "Content-Type: application/scim+json" \
  -d '{
    "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
    "Operations": [
      {
        "op": "add",
        "path": "members",
        "value": [
          { "value": "USER_ID_1" },
          { "value": "USER_ID_2" }
        ]
      }
    ]
  }'

SCIM Error Responses

When a SCIM request fails, the response body follows the SCIM error schema: { "schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"], "status": "400", "detail": "..." }. Common status codes include 400 (bad request), 404 (resource not found), 409 (conflict / duplicate), and 429 (rate limited).

Portal API (automation)

The Portal API lets your own backend automate everything you can do in the portal — manage users, clients, groups, roles, scopes, SSO connections, and settings — using a machine-to-machine credential. It is the same API the portal UI calls.

Base URL: https://portal-api.<your-domain>/api/v1. Requests authenticate with a Bearer access token; the tenant is taken from the token, not the URL.

Creating an API credential

In the portal, open Clients → Create API credential, pick an access level, and give it a name. Authagonal generates an OAuth client_credentials client wired for the Portal API and returns a client ID and secret.

Copy the secret immediately

The client secret is shown only once, right after creation. Store it in your secret manager before closing the dialog — if you lose it, delete the credential and create a new one.

Access levels

ScopeGrants
tenant:ownerFull access, including destructive owner-only actions such as deleting the entire tenant.
tenant:adminManage everything except owner-only actions — users, clients, SSO, groups, roles, branding, and settings.
tenant:developerManage clients, scopes, and provisioning apps.
tenant:supportRead and manage users for support tasks.

You can only grant what you hold

A credential can be no more privileged than the person creating it. An admin cannot mint an owner-scoped credential, and the platform administrative scope can never be granted to a credential.

Getting a token

Exchange the credential for an access token at your tenant's token endpoint — https://<your-tenant>.<your-domain>/connect/token — then send the token as a Bearer header to the Portal API. Tokens are valid for one hour.

Get a token, then call the API
# 1. Exchange the credential for an access token (your tenant's token endpoint)
curl -X POST https://acme.authagonal.io/connect/token \
  -d grant_type=client_credentials \
  -d client_id=api-3f2a... \
  -d client_secret=YOUR_CLIENT_SECRET \
  -d scope=tenant:admin

# Response: { "access_token": "ey...", "token_type": "Bearer", "expires_in": 3600 }

# 2. Call the Portal API with the access token
curl https://portal-api.authagonal.io/api/v1/users \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Endpoints

All paths are relative to the base URL and require a Bearer access token. The scope beside each group is the minimum credential access level it needs. List endpoints accept startIndex and count query parameters.

Clientstenant:developer

GET/api/v1/clients— List OAuth clients.

GET/api/v1/clients/{id}— Get a single client by ID.

POST/api/v1/clients— Create a client. Returns the client ID and, for confidential clients, a one-time secret.

PUT/api/v1/clients/{id}— Update a client (redirect URIs, grant types, token lifetimes, PKCE/PAR requirements).

DELETE/api/v1/clients/{id}— Delete a client.

POST/api/v1/clients/api-credential— Mint a machine-to-machine Portal API credential.

Userstenant:support

GET/api/v1/users— List users. Supports startIndex, count, and search (email / name prefix).

GET/api/v1/users/count— Total user count for the tenant.

GET/api/v1/users/stats/mfa— MFA enrollment statistics.

GET/api/v1/users/{id}— Get a single user.

POST/api/v1/users— Create a user with an email and password.

PUT/api/v1/users/{id}— Update a user (profile, email, enabled/blocked state).

DELETE/api/v1/users/{id}— Delete a user.

GET/api/v1/users/{id}/mfa— Get a user's enrolled MFA methods.

DELETE/api/v1/users/{id}/mfa— Reset a user's MFA enrollment.

Rolestenant:admin

GET/api/v1/roles— List roles.

POST/api/v1/roles— Create a role.

DELETE/api/v1/roles/{id}— Delete a role.

POST/api/v1/roles/assign— Assign a role to a user.

POST/api/v1/roles/unassign— Remove a role from a user.

Groupstenant:admin

GET/api/v1/groups— List groups.

GET/api/v1/groups/{id}— Get a group with its members.

POST/api/v1/groups— Create a group.

POST/api/v1/groups/{id}/members— Add members to a group.

DELETE/api/v1/groups/{groupId}/members/{userId}— Remove a member from a group.

DELETE/api/v1/groups/{id}— Delete a group.

GET/api/v1/group-role-mappings— List group-to-role mappings (roles granted at token issuance by group membership).

Scopestenant:developer

GET/api/v1/scopes— List API scopes.

POST/api/v1/scopes— Create a scope.

DELETE/api/v1/scopes/{name}— Delete a scope.

SSO connectionstenant:admin

GET/api/v1/saml/connections— List SAML connections.

POST/api/v1/saml/connections— Create a SAML connection.

DELETE/api/v1/saml/connections/{id}— Delete a SAML connection.

GET/api/v1/oidc/connections— List OIDC connections.

POST/api/v1/oidc/connections— Create an OIDC connection.

DELETE/api/v1/oidc/connections/{id}— Delete an OIDC connection.

GET/api/v1/sso/domains— List the domains routed to SSO connections (home-realm discovery).

Brandingtenant:admin

GET/api/v1/branding— Get the tenant branding (colors, logo, supported languages).

PUT/api/v1/branding— Update the tenant branding.

Settingstenant:admin

GET/api/v1/settings— Get tenant settings (webhooks, public sign-up, token policy).

PUT/api/v1/settings— Update tenant settings.

POST/api/v1/settings/webhook-secret/regenerate— Rotate the webhook signing secret.

POST/api/v1/settings/test-email— Send a test email with the current email configuration.

Custom domains & emailtenant:admin

GET/api/v1/custom-domains— List custom login domains and their verification status.

POST/api/v1/custom-domains— Add a custom domain.

POST/api/v1/custom-domains/{domain}/verify— Trigger DNS verification for a custom domain.

DELETE/api/v1/custom-domains/{domain}— Remove a custom domain.

GET/api/v1/email/domains— List sender email domains.

Audit logtenant:admin

GET/api/v1/audit— Query the tenant audit log.

Provisioning users via SCIM

For bulk user and group provisioning from an IdP (Entra, Okta), use the SCIM 2.0 API rather than these endpoints.

Example: create a user

POST /api/v1/users
curl -X POST https://portal-api.authagonal.io/api/v1/users \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "ada@acme.com",
    "password": "S3cure-temp-passw0rd",
    "firstName": "Ada",
    "lastName": "Lovelace"
  }'

# 200 OK
# { "userId": "8f3a...", "email": "ada@acme.com" }

Anything the UI can do

The Portal API exposes the same endpoints the portal UI uses, so any operation you can perform in the portal can be automated — subject to the credential's access level.

Authentication Flows

Authentication flows cover how end users interact with your Authagonal tenant — logging in, registering, resetting passwords, and setting up MFA. These endpoints are used by the hosted login page and can be called directly if you are building a custom login UI.

Login

POST /api/auth/login

Authenticates a user with email and password. On success, signs a session cookie and returns the user profile. If MFA is configured, the response indicates that a second factor is required before the session is fully established.

Request body:

Login request
{
  "email": "user@example.com",
  "password": "correct-horse-battery-staple"
}

Success response:

FieldTypeDescription
userIdstringUnique user identifier
emailstringUser email address
namestringFull display name
mfaAvailablebooleanWhether the user has MFA methods enrolled

MFA required response: When the user has MFA enrolled, the response includes mfaRequired: true along with a challengeId and a methods array listing available MFA methods.

MFA setup required response: When the tenant requires MFA but the user has not yet enrolled, the response includes mfaSetupRequired: true with a setupToken for the enrollment flow.

Error responses:

Error CodeHTTP StatusDescription
invalid_credentials401Email or password is incorrect
account_disabled403The account has been deactivated by an admin
email_not_confirmed403The user has not verified their email address
locked_out423Account is temporarily locked (includes retryAfter in seconds)
sso_required409Email domain has SSO configured (includes redirectUrl)

SSO check: If the user's email domain has an SSO connection configured, the login endpoint returns sso_required with a redirectUrl. The client should redirect the user to the SSO provider.

Account lockout: After maxFailedAttempts consecutive failed login attempts, the account is locked for lockoutDurationMinutes. Both values are configurable in tenant settings.

Hosted Login Page

The login endpoint is typically called by the hosted login page, not directly by your application. Use the OIDC authorization code flow to initiate authentication — your users will be redirected to the hosted login page automatically.

Registration

POST /api/auth/register

Creates a new user account. A verification email is sent automatically — the user must verify their email before they can log in.

Request body:

Registration request
{
  "email": "newuser@example.com",
  "password": "a-strong-password-here",
  "firstName": "Jane",
  "lastName": "Smith"
}
FieldRequiredDescription
emailYesEmail address (must be unique)
passwordYesMust meet the tenant password policy
firstNameNoFirst name
lastNameNoLast name

Success: 201 Created with the userId of the new account.

Error responses:

Error CodeHTTP StatusDescription
email_already_registered409An account with this email already exists
weak_password400Password does not meet the tenant password policy
rate_limited429Too many registration attempts
provisioning_rejected422A provisioning webhook rejected the registration

Password Policy

Check the tenant's password requirements before submission via GET /api/auth/password-policy. This returns minimum length, required character classes, and whether breached password checking is enabled.

Password Reset

POST /api/auth/forgot-password

Requests a password reset email. The endpoint always returns a success response regardless of whether the email exists, to prevent email enumeration.

Forgot password request
{
  "email": "user@example.com"
}

POST /api/auth/reset-password

Resets the user's password using the token from the email link.

Reset password request
{
  "token": "RESET_TOKEN_FROM_EMAIL",
  "newPassword": "new-strong-password"
}

Side effects of a successful password reset:

  • Failed login attempt counter is reset to zero
  • All existing refresh tokens are revoked
  • A new security stamp is generated (invalidating all existing sessions)

MFA Setup & Verification

Authagonal supports three MFA methods: TOTP (authenticator apps), WebAuthn (security keys and biometrics), and single-use recovery codes.

TOTP Setup

POST /api/auth/mfa/totp/setup — Returns a QR code data URI and a manual entry key. The user scans the QR code with their authenticator app (Google Authenticator, Authy, 1Password, etc.), then confirms enrollment.

POST /api/auth/mfa/totp/confirm — Confirms TOTP enrollment by validating a 6-digit code from the authenticator app.

Confirm TOTP enrollment
{
  "code": "123456"
}

WebAuthn Setup

POST /api/auth/mfa/webauthn/setup — Returns credential creation options for the WebAuthn API. The browser calls navigator.credentials.create() with these options.

POST /api/auth/mfa/webauthn/confirm — Confirms WebAuthn enrollment by submitting the attestation response from the browser.

Recovery Codes

POST /api/auth/mfa/recovery/generate — Generates 10 single-use 8-character recovery codes. Each code can be used exactly once to bypass MFA.

Recovery Codes Are Shown Only Once

Recovery codes are displayed only at the time of generation and cannot be retrieved later. If a user loses both their authenticator device and their recovery codes, an admin must manually delete their MFA credential from the portal before they can log in again.

MFA Verification

POST /api/auth/mfa/verify — Completes the MFA challenge after a successful password login.

FieldRequiredDescription
challengeIdYesThe challenge ID from the login response
methodYes"totp", "recovery", or "webauthn"
codeTOTP / Recovery6-digit TOTP code or 8-character recovery code
assertionWebAuthnThe assertion response from navigator.credentials.get()

MFA Status

GET /api/auth/mfa/status — Returns the user's currently enrolled MFA methods.

SSO Login Flow

Authagonal supports both SAML 2.0 and OIDC-based SSO connections. Domain-based routing automatically detects which SSO provider to use based on the user's email address.

SSO Check

GET /api/auth/sso-check?email=user@acme.com

FieldTypeDescription
ssoRequiredbooleanWhether the email domain requires SSO
providerTypestring"saml" or "oidc"
connectionIdstringThe SSO connection identifier
redirectUrlstringThe URL to redirect the user to for SSO login

SAML Flow

The user is redirected to GET /saml/{connectionId}/login which sends a SAML AuthnRequest to the identity provider. The IdP authenticates the user and posts a SAML response back to the Assertion Consumer Service (ACS) endpoint. Authagonal validates the assertion, creates or updates the user, and signs a session cookie.

SAML metadata for configuring your IdP is available at GET /saml/{connectionId}/metadata.

OIDC Flow

The user is redirected to GET /oidc/{connectionId}/login which redirects to the upstream identity provider with PKCE. After the user authenticates, the callback at /oidc/callback exchanges the authorization code, validates the ID token, and creates or updates the user.

JIT Provisioning: Both SAML and OIDC flows support just-in-time provisioning. If the user does not already exist in the tenant, they are created automatically from the identity provider's claims. If they do exist, their profile attributes are updated to match the latest values from the provider.

Domain-Based Routing

Domain-based routing means your users don't need to know which SSO provider they use. Entering their email address is enough — Authagonal matches the domain to the correct SSO connection and redirects automatically.

Build a custom login UI

Replace Authagonal's hosted login, registration, password-reset and MFA screens with your own UI, while Authagonal keeps handling authentication, MFA, SSO, sessions and token issuance. Two routes: use our React component library, or call the auth API directly from any framework. It's opt-in — enable Custom login UI in tenant settings first.

Prerequisite: a custom domain on your root

The login session is a first-party cookie, so your UI and the Authagonal auth server must share a registrable domain. Point a custom auth domain at Authagonal on the same root your app runs on — e.g. auth at login.acme.com, app at app.acme.com. The Custom login UI setting stays disabled until an active custom domain exists.

Your UIAuth hostWorks?
app.acme.comlogin.acme.com✅ same root
acme.comauth.acme.com✅ same root
app.acme.comacme.authagonal.io❌ cross-site
myapp.iologin.acme.com❌ cross-site

Why a custom domain is required

A cross-site session cookie would be a third-party cookie — which browsers (Safari, Chrome) are phasing out. Keeping auth on your own root makes the cookie first-party and future-proof, and it's what the platform enforces: cross-origin auth calls are only honoured from an origin that shares the auth host's root domain.

Also add your UI's origin (e.g. https://app.acme.com) to your OAuth client's Allowed CORS origins — the same list you set for the token exchange.

React: @authagonal/login

npm i @authagonal/login ships the auth logic and UI as one package — the same one Authagonal's hosted login is built on. Pick your altitude:

  • Full app — drop in App and theme it via branding.
  • Compose pages — use LoginPage, MfaChallengePage, ResetPasswordPage… inside your own layout.
  • Primitives + logic — build your own screens with AuthLayout/Button/Input and the API client (login, mfaVerify, forgotPassword, …).
A custom screen using the @authagonal/login API
import { AuthLayout, Input, Button, login, ApiRequestError } from '@authagonal/login';

function MyLogin() {
  async function onSubmit(email: string, password: string) {
    try {
      const res = await login({ email, password });        // POST /login (sets the session cookie)
      if (res.mfaRequired) {/* render your MFA step → mfaVerify(...) */}
      else window.location.href = res.returnUrl;            // hand off to /connect/authorize
    } catch (e) {
      if (e instanceof ApiRequestError) {/* show e.message */}
    }
  }
  return <AuthLayout>{/* your own markup + <Input/> <Button/> */}</AuthLayout>;
}

Any framework: call the auth API

Not on React? Call the auth-flow endpoints directly (under /api/auth), then hand off to the standard OIDC /connect/authorize flow. Send credentials: 'include' so the session cookie is stored.

EndpointPurpose
POST /api/auth/loginAuthenticate; returns mfaRequired or a return URL
POST /api/auth/registerSelf-service registration (when enabled)
POST /api/auth/forgot-passwordStart a password reset
POST /api/auth/reset-passwordComplete a password reset
GET /api/auth/password-policyPassword policy (to render the rules)
POST /api/auth/mfa/*MFA setup + verification (TOTP, WebAuthn, recovery)

Use credentials: 'include'

The session is a cookie, so your fetches must send credentials. Cross-origin calls only succeed when Custom login UI is enabled and your origin shares the auth host's root domain — otherwise they're refused with 403.
Authenticate, then hand off to OIDC
# 1. Authenticate (browser fetch — credentials:'include' so the session cookie is stored)
curl -i -X POST https://login.acme.com/api/auth/login \
  -H "Content-Type: application/json" \
  -H "Origin: https://app.acme.com" \
  --data '{"email":"jane@acme.com","password":"..."}'
# (handle {"mfaRequired":true} → POST /api/auth/mfa/verify, then continue)

# 2. Hand off to the OAuth flow — top-level navigation to the authorize endpoint with PKCE:
# https://login.acme.com/connect/authorize?client_id=my-app&redirect_uri=...&response_type=code
#   &scope=openid%20profile%20email&code_challenge=...&code_challenge_method=S256
# The session cookie (same-site) authenticates the user; you get back a code → exchange at /connect/token.

Plans & Limits

Authagonal offers four plan tiers. All plans include all features — the only difference is the Monthly Active User (MAU) limit and overage pricing.

Plan Tiers

PlanMAU LimitOverageOverage Cost/User
Starter1,000No
Pro5,000Yes$0.04/user
Scale25,000Yes$0.025/user
Enterprise100,000Yes$0.015/user

Monthly Active Users (MAU)

A Monthly Active User is any unique user who successfully authenticates at least once during a billing month. Users provisioned via SCIM but who haven't logged in don't count toward your MAU total.

Overage — If your plan supports overage, users beyond the MAU limit are billed at the per-user rate shown in the plan table above. You can set an overage cap to limit your maximum spend for the billing period.

Enforcement — If your plan doesn't support overage (Starter), users beyond the MAU limit cannot log in until the next billing period or until you upgrade to a plan that supports overage.

Full Feature Set on Every Plan

All plans include the full feature set — SSO, SCIM, MFA, custom domains, branding, webhooks, audit log, and the portal.