Skip to main content
Version: v0.25.0 (Latest)

Issuing with the Authorization Code Flow

The pre-authorized code flow used earlier suits backend-initiated issuance where the subject is already known. The authorization code flow inverts this: the wallet sends the user to the authorization server to authenticate first, and the issuer resolves the subject's claims from the authenticated identity.

The offer​

An offer with an authorization_code grant carries no subject data. The issuer_state ties the wallet's authorization request back to this offer:

Create offer with authorization code grant

Endpoint: POST /api/oid4vci/v1/backend/credential/offers

Captured response: 201 Created

This captured endpoint is shown from the E2E run; it is not mapped to one of the generated EDK REST API reference pages.

Live against connected environment

Connect an environment to rewrite this call to real service bases and run it.

The flow​

The wallet reads the authorization server metadata, then drives a standard OAuth2 authorization code flow with PKCE:

Fetch authorization server metadata

Endpoint: GET /.well-known/oauth-authorization-server

Captured response: 200 OK

This captured endpoint is shown from the E2E run; it is not mapped to one of the generated EDK REST API reference pages.

Live against connected environment

Connect an environment to rewrite this call to real service bases and run it.

  1. The wallet opens the authorization endpoint with response_type=code, its client id, a redirect URI, the issuer_state, and a PKCE challenge.
  2. The user authenticates: against the hosted AS, or through a federated IdP registered in step 4 of this walkthrough.
  3. The wallet exchanges the authorization code for an access token at the token endpoint.
  4. The credential request proceeds exactly as in the pre-authorized flow: a proof of possession signed by the holder key, answered with the credential.

The issuance session on the backend moves through the same lifecycle states either way, so status polling and callbacks work identically for both grants.

Where the credential claims come from​

In the pre-authorized flow the caller supplies the subject's attributes when it creates the offer. The authorization code flow has no such moment, so the issuer has to resolve the claims from the authenticated identity instead. Two things have to line up for that.

The authorization server must hold the claims​

For a hosted authorization server the access token carries no identity claims, and introspection surfaces none. The authenticated user's claims live on the authorization server and are served from its UserInfo endpoint, scope-filtered by what the token was granted.

When the user authenticated through a federated IdP, the authorization server builds governed authentication evidence from the upstream identity. The federation binding's claimsMapping maps upstream source claims onto governed authentication targets for that evidence; it is not the UserInfo cache schema or the credential-claim selection policy. For example, the direction is source → target:

{
"claimsMapping": {
"sub": "subject",
"email": "email",
"name": "displayName"
}
}

The cached upstream UserInfo retains the upstream source claim names and values. Set this binding map when you register the binding:

POST /api/platform/config/v1/tenants/{tenantId}/authorization-servers/{authorizationServerId}/federation-bindings

The issuer must be allowed to read them​

Surfacing those claims to issuance is opt-in per tenant, and off by default:

oid4vci.issuer.surface-local-userinfo-to-issuance=true

With it enabled, the issuer reads the authorization server's UserInfo endpoint for the presented access token, including cached federated UserInfo claims, and forwards the result into the separate AuthSessionClaimSource issuance-pipeline step. The UserInfo endpoint remains available according to the authorization server's configuration; this flag controls forwarding its values into issuance. The access token itself may intentionally contain no identity claims. The subject is excluded from the surfaced claim set because it is already modeled separately. The default profile selection includes given_name, family_name, and job_title; email requires the separate email scope. employee_id requires an explicit claims.userinfo.employee_id filter or an explicitly configured scope mapping. If the UserInfo read is unavailable or misconfigured, token validation can still succeed with no surfaced claims, but a required-claim issuance pipeline may fail issuance; this path does not promise a silently incomplete credential.

The surfaced claims reach a credential only through the governed issuance pipeline and its claim selection. claimsMapping does not itself write credential claims. There is no per-credential- configuration mapping table that renames an ID token claim into a different credential claim: renaming and restructuring belong to the credential design and the semantic attribute set the design draws from. Where the authentication target and credential name differ, align them in the pipeline/design, not by treating the federation binding map as a credential-claim map.

note

A credential whose claims are assembled by the issuance pipeline resolves them through the pipeline's configured sources and claims bindings instead. UserInfo claims are visible to the pipeline as provenance fields, but the pipeline's own sources remain the place its claims come from.