---
title: "Authorization Code Flow"
description: "Full sequence diagram for issuing a credential via the OID4VCI Authorization Code Flow with an external IdP."
stack: "Community Stack (open source) — version 0.21.0"
stack_version: "0.21.0"
stack_comparison: https://docs.walt.id/community-vs-enterprise.md
canonical_url: https://docs.walt.id/community-stack/issuer2/protocol-flows/authorization-code-flow
generated: 2026-07-20
---
# Authorization Code Flow

This flow covers your application creating a credential offer, the wallet redirecting the holder to an external IdP to authenticate, and the Issuer2 API mapping the returned ID token claims into the credential. Set `"authMethod": "AUTHORIZED"` when [creating the offer](https://docs.walt.id/community-stack/issuer2/credential-offers/create-offer.md) to use it.

**Components involved:**
- Your Application — creates the credential offer and displays the QR code
- Wallet (Holder) — scans the QR code, redirects the holder to log in, completes the flow
- Issuer2 API — manages the issuance session, correlates `issuer_state`, applies `idTokenClaimsMapping`, and signs the credential
- External IdP — authenticates the holder and issues the ID token (registered in [`authentication-service.conf`](https://docs.walt.id/community-stack/issuer2/configurations/config-files/authentication-service.md))

**Note:**

**Trying this out?** You don't need your own identity provider to start. If you haven't configured `authentication-service.conf`, Issuer2 uses a [hosted walt.id test provider](https://docs.walt.id/community-stack/issuer2/configurations/config-files/authentication-service.md#default-test-provider) with ready-made login accounts (e.g. `jane@walt.id` / `jane`), so you can walk through the whole flow immediately.

**Error:**

For production, this flow requires **your own** external OAuth/OIDC provider (e.g. Keycloak, Microsoft Entra ID, Auth0) registered in `authentication-service.conf` before you create an `AUTHORIZED` offer. See [Authentication Service Configuration](https://docs.walt.id/community-stack/issuer2/configurations/config-files/authentication-service.md) to set one up.

```mermaid
sequenceDiagram
    participant App as Your Application
    participant Wallet as Wallet<br/>(Holder)
    participant Issuer as Issuer2 API
    participant IdP as External IdP<br/>(e.g. Keycloak)

    App->>Issuer: POST /issuer2/credential-offers<br/>{profileId, authMethod: AUTHORIZED, issuerStateMode: INCLUDE}
    activate Issuer
    Issuer->>Issuer: Create issuance session<br/>and issuer_state
    Issuer-->>App: 201 Created<br/>{credentialOffer: "openid-credential-offer://..."}
    deactivate Issuer

    App->>App: Generate QR code with<br/>credential offer URL
    App-->>Wallet: Display QR code

    Wallet->>Wallet: Scan QR code
    Wallet->>Issuer: GET /.well-known/openid-credential-issuer/openid4vci
    Issuer-->>Wallet: Issuer metadata

    Wallet->>Issuer: GET /authorize<br/>(response_type=code, issuer_state)
    activate Issuer
    Issuer->>Issuer: Resolve issuance session from issuer_state
    Issuer-->>Wallet: 302 redirect to IdP authorizeUrl
    deactivate Issuer

    Wallet-->>IdP: Follow redirect to authorizeUrl
    IdP->>IdP: User authenticates
    IdP-->>Issuer: Redirect to<br/>/openid4vci/external/oauth/callback?code=...&state=...

    activate Issuer
    Issuer->>IdP: Exchange code at accessTokenUrl<br/>(handled transparently by the issuer's OAuth client)
    IdP-->>Issuer: id_token (+ access_token)

    Issuer->>Issuer: Look up session by external OAuth state,<br/>apply idTokenClaimsMapping into credentialData

    Issuer-->>Wallet: 302 redirect back to wallet's redirect_uri<br/>with issuer's own authorization code
    deactivate Issuer

    Wallet->>Issuer: POST /token<br/>(authorization_code)
    Issuer-->>Wallet: access_token

    Wallet->>Issuer: POST /nonce
    activate Issuer
    Issuer->>Issuer: Generate fresh c_nonce
    Issuer-->>Wallet: {c_nonce}
    deactivate Issuer

    Wallet->>Wallet: Generate proof of possession<br/>(incl. sign c_nonce with holder key)

    Wallet->>Issuer: POST /credential<br/>(access_token, proof)
    activate Issuer
    Issuer->>Issuer: Sign credential using<br/>mapped claims
    Issuer-->>Wallet: 200 OK<br/>{credential}
    deactivate Issuer

    Wallet->>Wallet: Store credential
```

**Note:**

This diagram is a simplified overview focused on how the Issuer2 API behaves. It omits some protocol details (e.g. metadata discovery of the Nonce Endpoint, PKCE, and the separate authorization-server metadata fetch). For the complete, normative sequence see the [OpenID for Verifiable Credential Issuance 1.0 specification](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html).

## Understanding `issuer_state`

`issuer_state` is how the Issuer2 API correlates the authorization request it receives back at `/authorize` with the issuance session created when the offer was made. Set `"issuerStateMode": "INCLUDE"` when creating the offer to have it included.

By default, `issuer_state` is **not** forwarded to the external IdP — it's only used internally by the issuer. Set `forwardIssuerStateToAuthorizationServer = true` in `authentication-service.conf` only if your IdP integration specifically needs to receive it as a query parameter on the outgoing authorize request.

## Related

- [Authentication Service Configuration](https://docs.walt.id/community-stack/issuer2/configurations/config-files/authentication-service.md) — Register your OAuth/OIDC provider
- [Issuer Profiles — ID Token Claim Mapping](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles.md#id-token-claim-mapping) — Map ID token claims into credential fields
- [Create an Offer](https://docs.walt.id/community-stack/issuer2/credential-offers/create-offer.md) — `issuerStateMode` and other offer parameters
- [Pre-Authorized Code Flow](https://docs.walt.id/community-stack/issuer2/protocol-flows/pre-authorized-flow.md) — The simpler alternative, when user login isn't needed
