---
title: Authentication Settings
project: Authaz
updated: 2026-04-29T17:34:54.429Z
---


# Authentication Settings

The **Settings** card under *Application → Authentication* is the OAuth 2.0 / Universal Login configuration surface. It's where you tell Authaz: *which URLs your app will be redirected to, what scopes it can request, how long tokens live, and how sessions behave*. Most production issues — `redirect_uri_mismatch`, expired-token loops, "user keeps getting logged out" — trace back to this page.

```bash
curl -X PUT https://your-app.authaz.io/api/v1/applications/{appId}/universal-login \
  -H "X-API-Key: $AUTHAZ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": true,
    "redirectUris": [
      "https://yourapp.com/auth/callback",
      "http://localhost:3000/auth/callback"
    ],
    "postLogoutRedirectUris": [
      "https://yourapp.com",
      "http://localhost:3000"
    ],
    "allowedScopes": ["openid", "profile", "email", "offline_access"],
    "requireTenantHint": false,
    "accessTokenLifetime": 3600,
    "refreshTokenLifetime": 2592000,
    "authorizationCodeLifetime": 600,
    "sessionTimeoutMinutes": 480,
    "rememberMeTimeoutMinutes": 43200,
    "applicationCredentialId": "cred_01h..."
  }'
```

## Where it lives

**Dashboard → Application → Authentication → Settings.**

## Redirect URIs

`redirectUris` is the allow-list of URLs Authaz will redirect users back to after authentication. Every URL your app uses must be listed here.

```json
{
  "redirectUris": [
    "https://yourapp.com/auth/callback",
    "https://staging.yourapp.com/auth/callback",
    "http://localhost:3000/auth/callback"
  ]
}
```

Rules Authaz enforces (per OAuth 2.0):

- **Exact match.** `https://yourapp.com/callback` does *not* match `https://yourapp.com/callback/`. Trailing slashes matter.
- **Scheme matters.** `https` and `http` are different. Production should be `https` only; localhost is the standard exception.
- **No wildcards in production.** `https://*.yourapp.com/callback` is not valid. Add each subdomain explicitly. (Localhost can use any port — `http://localhost:*` is treated as a special case.)
- **Query strings are stripped before matching.** `?utm=...` on the way out is fine.

A mismatch returns `error=invalid_request, error_description=redirect_uri_mismatch` and the user never reaches your callback. The [audit log](../audit-logs.md) records every mismatch so you can see exactly what was attempted.

`postLogoutRedirectUris` is the same allow-list but for the logout flow — where users land after `POST /oauth2/logout`. Same matching rules.

## Allowed scopes

`allowedScopes` is the menu of OAuth scopes your application is allowed to request. Authaz rejects any token request whose `scope` parameter contains a value not on this list.

Default: `["openid", "profile", "email"]`. Standard OIDC scopes plus any custom scopes you've defined for [resources](../authorization/resources.md).

| Scope | What it grants in the access token |
|-------|-----------------------------------|
| `openid` | Required for OIDC. Includes a stable `sub` claim. |
| `profile` | `name`, `given_name`, `family_name`, `picture`. |
| `email` | `email`, `email_verified`. |
| `offline_access` | A refresh token alongside the access token. |
| Custom (e.g. `invoices:read`) | Permission scopes you've defined in your resource catalog. |

Add only what your app actually uses. Over-broad scopes leak data into tokens and bloat their size.

## Token lifetimes

All four are in **seconds** (except the two session/remember-me fields, which are in **minutes**). Defaults below match the dashboard.

| Setting | Default | What it controls |
|---------|---------|-----------------|
| `accessTokenLifetime` | `3600` (1 h) | How long access tokens stay valid before needing refresh. |
| `refreshTokenLifetime` | `2592000` (30 d) | Maximum age of refresh tokens. |
| `authorizationCodeLifetime` | `600` (10 min) | How long an authorization code is valid for exchange — short on purpose. |
| `sessionTimeoutMinutes` | `480` (8 h) | How long a Universal Login session stays usable. After this, users sign in again. |
| `rememberMeTimeoutMinutes` | `43200` (30 d) | How long "Remember me" extends the session. |

### Picking access-token lifetime

Shorter tokens = better revocation latency, more refresh round-trips. Longer = fewer round-trips, slower revocation when needed.

| Use case | Suggested lifetime |
|----------|-------------------|
| High-security (banking, healthcare) | 5–15 min |
| Standard B2B SaaS | 30–60 min (default) |
| Long-running native apps where refresh is awkward | 2–4 h |

Don't go above 24 h. Refresh tokens exist for the long-lived case.

### Refresh-token rotation

Authaz rotates refresh tokens by default — every successful refresh issues a new refresh token and invalidates the previous one. If your client retries an old refresh token, Authaz returns `400 invalid_grant` and revokes the entire refresh-token family. This catches token theft.

Always store the refresh token returned in each response, not the original.

## requireTenantHint

```json
{ "requireTenantHint": true }
```

When `true`, every OAuth authorize request must include a `tenant_id` parameter:

```
https://your-app.authaz.io/oauth2/authorize?
  client_id=YOUR_APP_ID
  &redirect_uri=...
  &tenant_id=tenant_acme         ← required
  &response_type=code
  &scope=openid+profile
```

Authaz will use that tenant for the entire authentication flow (Universal Login UX, provider routing, role resolution, the `tenant_id` claim on the issued tokens). Requests without `tenant_id` are rejected with `400 missing_tenant_hint`.

Use this when:

- Your app's URL structure already encodes the tenant (e.g. `acme.yourapp.com` calls `/oauth2/authorize?tenant_id=tenant_acme`).
- You don't want users to see a tenant picker on Universal Login.
- You're running [isolated auth](../tenancy-customization.md#isolated-auth-stack) and the tenant must be known up front to pick the right providers.

Leave it `false` (the default) when:

- A user might belong to multiple tenants and should choose at login time.
- Your application is single-tenant (the parameter would be ignored anyway).

## Session policy

`sessionTimeoutMinutes` is the absolute lifetime of a Universal Login session — after this many minutes, the user must sign in again, no matter how active they are.

`rememberMeTimeoutMinutes` is the extended lifetime when the user clicks **Remember me**. The default of 43200 (30 days) is reasonable for most apps; banking and healthcare apps usually want this much shorter (~480 min, matching the regular session) or disabled entirely.

Both work in concert with the access-token lifetime: the access token is the per-request credential, the session is the cookie that lets Universal Login skip re-authentication on the *next* token request.

## Application credential

`applicationCredentialId` links this configuration to an OAuth client credential — the `client_id` and `client_secret` your app sends on `POST /oauth2/token`. The dashboard creates one for you on first save; you can also pre-create one under the [API Keys](./api-keys.md) page and link it here.

The credential carries:

- `client_id` — public, included in every authorize URL.
- `client_secret` — confidential, only sent server-side from your backend.

Rotate the secret when:

- It's been leaked.
- A team member with access leaves.
- Annually as part of routine hygiene.

Rotation issues a new secret with a 24-hour grace period — both the old and new secrets work during that window so you can deploy without downtime.

## Multi-tenant: per-tenant settings

When [Isolated Auth Stack](../tenancy-customization.md#isolated-auth-stack) is on, each tenant gets its own settings page with its own redirect URIs, token lifetimes, and `requireTenantHint`:

```bash
curl -X PUT https://your-app.authaz.io/api/v1/applications/{appId}/tenants/{tenantId}/universal-login \
  -H "X-API-Key: $AUTHAZ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "redirectUris": ["https://acme.yourapp.com/auth/callback"],
    "accessTokenLifetime": 1800
  }'
```

Tenants without an override fall back to the application-level config.

## Diagnosing common errors

| Error | Cause | Fix |
|-------|-------|-----|
| `redirect_uri_mismatch` | The callback URL isn't on `redirectUris`. | Add it (exact match). |
| `invalid_scope` | A requested scope isn't on `allowedScopes`. | Add it, or stop requesting it. |
| `missing_tenant_hint` | `requireTenantHint` is on, but `tenant_id` wasn't included. | Add `tenant_id=` to the authorize URL. |
| `invalid_grant` on refresh | Refresh token was rotated and an old one was used. | Always use the latest refresh token from the most recent response. |
| `code expired` | More than `authorizationCodeLifetime` seconds passed before exchange. | Exchange codes immediately on callback; don't queue them. |

## Next steps

- [Tokens](../tokens.md) — what's inside the access, ID, and refresh tokens these settings shape.
- [Tenancy Customization](../tenancy-customization.md) — when these settings can be overridden per tenant.
- [cURL quickstart](../quickstart/curl.md) — the OAuth flow these settings drive.
