Table of Contents

Discovery and JWKS

Before it sends anyone anywhere, a client or an API reads the server's metadata: where each endpoint is, which grants, scopes and algorithms it supports, and where its signing keys are published. The keys are what an API checks a JWT access token's signature against, so it fetches them from jwks_uri and matches a token's kid to one of them.

Follows: OpenID Connect Discovery 1.0, RFC 7517 (JWK).

Recorded from the demo host (src/Fdyay.IdentityServer.Host) by the DiscoveryFlow test on every CI run. A value in braces, such as {access_token_1}, is one that differs on every run; the same placeholder is the same value wherever it appears. See how these pages are made.

sequenceDiagram
    participant Client
    participant AuthorizationServer as Authorization Server
    Client->>AuthorizationServer: GET /.well-known/openid-configuration
    AuthorizationServer-->>Client: 200 OK
    Client->>AuthorizationServer: GET /.well-known/openid-configuration/jwks
    AuthorizationServer-->>Client: 200 OK

1. Read the discovery document

Client → Authorization Server

GET /.well-known/openid-configuration HTTP/1.1

Authorization Server → Client

HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8

{
  "issuer": "http://localhost",
  "jwks_uri": "http://localhost/.well-known/openid-configuration/jwks",
  "authorization_endpoint": "http://localhost/connect/authorize",
  "token_endpoint": "http://localhost/connect/token",
  "userinfo_endpoint": "http://localhost/connect/userinfo",
  "end_session_endpoint": "http://localhost/connect/endsession",
  "request_parameter_supported": true,
  "request_uri_parameter_supported": false,
  "prompt_values_supported": [
    "none",
    "login",
    "consent",
    "select_account"
  ],
  "check_session_iframe": "http://localhost/connect/checksession",
  "revocation_endpoint": "http://localhost/connect/revocation",
  "introspection_endpoint": "http://localhost/connect/introspect",
  "device_authorization_endpoint": "http://localhost/connect/deviceauthorization",
  "backchannel_authentication_endpoint": "http://localhost/connect/ciba",
  "backchannel_token_delivery_modes_supported": [
    "poll",
    "ping",
    "push"
  ],
  "backchannel_user_code_parameter_supported": true,
  "backchannel_authentication_request_signing_alg_values_supported": [
    "RS256",
    "RS384",
    "RS512",
    "PS256",
    "PS384",
    "PS512",
    "ES256",
    "ES384",
    "ES512"
  ],
  "pushed_authorization_request_endpoint": "http://localhost/connect/par",
  "scopes_supported": [
    "openid",
    "profile",
    "email",
    "api",
    "api.admin",
    "api.metrics",
    "reports",
    "IdentityServerApi",
    "ledger.read",
    "orders.write",
    "offline_access"
  ],
  "claims_supported": [
    "sub",
    "name",
    "family_name",
    "given_name",
    "middle_name",
    "nickname",
    "preferred_username",
    "profile",
    "picture",
    "website",
    "gender",
    "birthdate",
    "zoneinfo",
    "locale",
    "updated_at",
    "email",
    "email_verified"
  ],
  "subject_types_supported": [
    "public"
  ],
  "id_token_signing_alg_values_supported": [
    "RS256"
  ],
  "dpop_signing_alg_values_supported": [
    "RS256",
    "RS384",
    "RS512",
    "PS256",
    "PS384",
    "PS512",
    "ES256",
    "ES384",
    "ES512"
  ],
  "request_object_signing_alg_values_supported": [
    "RS256",
    "RS384",
    "RS512",
    "PS256",
    "PS384",
    "PS512",
    "ES256",
    "ES384",
    "ES512"
  ],
  "response_types_supported": [
    "code"
  ],
  "response_modes_supported": [
    "form_post",
    "query",
    "fragment"
  ],
  "authorization_response_iss_parameter_supported": true,
  "grant_types_supported": [
    "authorization_code",
    "client_credentials",
    "refresh_token",
    "urn:ietf:params:oauth:grant-type:device_code",
    "urn:openid:params:grant-type:ciba",
    "urn:fdyay:params:oauth:grant-type:demo-delegation",
    "urn:ietf:params:oauth:grant-type:token-exchange"
  ],
  "code_challenge_methods_supported": [
    "S256"
  ],
  "token_endpoint_auth_methods_supported": [
    "private_key_jwt",
    "tls_client_auth",
    "self_signed_tls_client_auth",
    "client_secret_basic",
    "client_secret_post"
  ],
  "token_endpoint_auth_signing_alg_values_supported": [
    "RS256",
    "RS384",
    "RS512",
    "PS256",
    "PS384",
    "PS512",
    "ES256",
    "ES384",
    "ES512"
  ],
  "revocation_endpoint_auth_methods_supported": [
    "private_key_jwt",
    "tls_client_auth",
    "self_signed_tls_client_auth",
    "client_secret_basic",
    "client_secret_post"
  ],
  "revocation_endpoint_auth_signing_alg_values_supported": [
    "RS256",
    "RS384",
    "RS512",
    "PS256",
    "PS384",
    "PS512",
    "ES256",
    "ES384",
    "ES512"
  ],
  "introspection_endpoint_auth_methods_supported": [
    "private_key_jwt",
    "tls_client_auth",
    "self_signed_tls_client_auth",
    "client_secret_basic",
    "client_secret_post"
  ],
  "introspection_endpoint_auth_signing_alg_values_supported": [
    "RS256",
    "RS384",
    "RS512",
    "PS256",
    "PS384",
    "PS512",
    "ES256",
    "ES384",
    "ES512"
  ]
}

2. Fetch the signing keys from jwks_uri

One RSA key, because the demo host signs with RS256 and has not rotated. During a rotation the next key is published here before it signs anything, so an API that caches this document already knows it.

Client → Authorization Server

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

Authorization Server → Client

HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8

{
  "keys": [
    {
      "kty": "RSA",
      "use": "sig",
      "kid": "{key_id_1}",
      "alg": "RS256",
      "n": "{rsa_modulus_1}",
      "e": "AQAB"
    }
  ]
}