OAuth Scopes

Authagonal supports both built-in OAuth/OIDC scopes and custom scopes managed at runtime. Custom scopes are persisted, advertised via the discovery document, and surfaced on the consent screen alongside built-ins.

Built-in Scopes

These scopes are always available and do not need to be registered:

Scope Purpose
openid Required to initiate an OIDC flow. Issues an ID token.
profile Standard profile claims (name, family_name, given_name, etc.)
email Email address and email_verified claims
offline_access Issues a refresh token alongside the access token

Custom Scopes

Custom scopes are managed through the admin API at /api/v1/scopes. They require a JWT access token with the authagonal-admin scope (configurable via AdminApi:Scope).

Scope Model

public sealed class Scope
{
    public required string Name { get; set; }
    public string? DisplayName { get; set; }
    public string? Description { get; set; }
    public bool Emphasize { get; set; }
    public bool Required { get; set; }
    public bool ShowInDiscoveryDocument { get; set; } = true;
    public List<string> UserClaims { get; set; } = [];
    public DateTimeOffset CreatedAt { get; set; }
    public DateTimeOffset? UpdatedAt { get; set; }
}
Field Description
Name The scope identifier sent in token requests (e.g., billing.read)
DisplayName Human-readable name shown on the consent screen
Description Longer description shown on the consent screen
Emphasize If true, the consent screen highlights this scope as sensitive
Required If true, the user cannot deselect this scope when consenting
ShowInDiscoveryDocument If true, the scope appears in /.well-known/openid-configuration under scopes_supported
UserClaims Claims added to the access token when this scope is granted

Admin Endpoints

List Scopes

GET /api/v1/scopes

Returns { "scopes": [ ... ] }.

Get Scope

GET /api/v1/scopes/{name}

Returns the scope or 404 if not found.

Create Scope

POST /api/v1/scopes
Content-Type: application/json

{
  "name": "billing.read",
  "displayName": "Billing — read-only",
  "description": "View invoices and payment history",
  "emphasize": false,
  "required": false,
  "showInDiscoveryDocument": true,
  "userClaims": ["billing_plan"]
}

Returns 201 Created with the scope. Returns 409 if a scope with the same name already exists.

Update Scope

PUT /api/v1/scopes/{name}
Content-Type: application/json

{
  "displayName": "Billing — read",
  "description": "View invoices",
  "emphasize": true
}

Only supplied fields are updated; omitted fields retain their current values.

Delete Scope

DELETE /api/v1/scopes/{name}

Returns 204 No Content. Tokens already issued that include this scope remain valid until they expire — revoke them explicitly if needed (see Admin API token revocation).

Discovery Document

Scopes with ShowInDiscoveryDocument = true appear under scopes_supported in /.well-known/openid-configuration. Built-in scopes are always advertised.

{
  "scopes_supported": ["openid", "profile", "email", "offline_access", "billing.read"]
}

When a client requests a scope that is not in its consent-skip list, the consent page lists each requested scope by DisplayName (falling back to Name) with the Description underneath. Scopes with Emphasize = true receive a distinct visual treatment. Required scopes cannot be deselected.

See OAuth Consent Screen for the user-facing flow.

Dynamic Client Registration

Clients registered via Dynamic Client Registration may only request scopes that are either built-in or previously created via the admin API. Unknown scopes are rejected with invalid_scope.