FortID LogoFortID

How to Issue a Verifiable Credential using Authorization Code Flow

A guide on issuing an EUDI Verifiable Credential using an Authorization Server and the Authorization Code flow.

This guide walks you through issuing a Verifiable Credential (VC) using the Authorization Code flow defined by OpenID4VCI.
In this flow, an Authorization Server (AS) authenticates the user, issues an access token, and can inject or override credential claims during issuance.
It focuses on the key differences compared to the “standard” credential issuance flow described in How to Issue a Verifiable Credential, and highlights the additional concepts and steps introduced when using an Authorization Server.

The workflow:

  1. Create an Issuer
  2. Assign a Schema to the Issuer with Authorization Server mapping
  3. Initiate issuance using Authorization Code
  4. Complete the flow in the wallet (user authentication)
  5. Check issuance session status

Prerequisites

Before issuing a credential:

  • The user must have an EUDI-compliant wallet installed.
  • Your Authorization Server must be reachable and configured to support the Authorization Code flow as defined in documentation

If you're unsure whether the wallet requirement is met, refer to: About FortID Reference Wallet


What changes with Authorization Code Flow?

In the Pre-Authorized Code flow, the Issuer must fully define claims at initiation time.

In the Authorization Code flow:

  • The wallet is sent to an Authorization Server (AS) to authenticate the user.
  • The wallet service requests access token (it must be a JWT) from the AS.
  • The wallet service uses that access token to obtain VC from the Issuer (OID4VCI protocol endpoints).
  • Claims can be partially defined during initiation, and then overwritten or injected by claims from the AS token based on mapping rules configured on the schema.

1. Create an Issuer

Create an issuer identity in the Issuer Service.

curl -X POST "https://eis.fortid.com/control/issuer" \
  -H "Content-Type: application/json" \
  -d '{
    "issuerId": "mup-issuer"
  }'

This establishes:

  • a unique issuer identifier (issuerId)
  • issuer cryptographic identity (signing keys)
  • issuer metadata endpoints (including .well-known)

If you provide display metadata during creation, wallets can show issuer branding (name, locale, colors, logo) in a user-friendly way.


2. Assign a Schema to the Issuer with Authorization Server mapping

Before the issuer can issue a specific credential, it must be configured with support for the relevant schema (credential configuration).

For Authorization Code flow, schema assignment also defines:

  • The Authorization Server URL(s) used for issuance
  • A mapping that describes how claims from the Authorization Server’s access token should be translated into credential claim paths

How the mapping works (conceptually)

The top-level keys are Authorization Server base URLs. Under each AS URL, you define identity providers / methods (e.g., google, local, credentials) Each entry maps:

  • a claim path in the access token (left side)
  • into a target claim path in the credential (right side, as [credentialConfigurationId, claimName])

If a claim is already set in the issuance initiation request, the Access Token claim can overwrite it (based on the configured rules).

Multiple Authorization Servers and methods

You can define multiple authorization servers and different “methods” (or identity providers) per server (e.g., google, local, credentials).

curl -X POST "https://eis.fortid.com/control/issuer/mup-issuer/add-schema" \
  -H "Content-Type: application/json" \
  -d '{
    "schemaId": "eu.europa.ec.eudi.pid_mdoc",
    "authorizationServer" : {
      "https://authorization-server.com/realms/fortid": {
        "google": {
          "username": ["eu.europa.ec.eudi.pid_mdoc.HR", "given_name"]
        },
        "credentials": {
          "as.inner.db.firstname": ["eu.europa.ec.eudi.pid_mdoc.HR", "given_name"],
          "as.inner.db.surname": ["eu.europa.ec.eudi.pid_mdoc.HR", "family_name"]
        }
      },
      "https://authorization-server.com/realms/gov": {
        "local": {
          "firstname": ["eu.europa.ec.eudi.pid_mdoc.HR", "given_name"],
          "lastname": ["eu.europa.ec.eudi.pid_mdoc.HR", "family_name"]
        }
      }
    },
    "credentialMetadata": {
      "credential_configuration_id": "eu.europa.ec.eudi.pid_mdoc.HR",
      "display": [
        {
          "name": "Croatian Identity Card",
          "locale": "en-US",
          "background_color": "#12107c",
          "text_color": "#FFFFFF",
          "logo": {
            "uri": "https://mup.hr/public/logo.png",
            "alt_text": "a square logo"
          }
        }
      ]
    }
  }'

3. Initiate issuance using Authorization Code

To start issuance, you initiate a new issuance session for:

  • a specific issuerId
  • a specific credentialConfigurationId
  • a set of initial claims (optional/partial, depending on your setup)

To request the Authorization Code flow, you set protocolConfig.grant_type to an AuthorizationCode object.

curl -X POST "https://eis.fortid.com/control/issuer/mup-issuer/initiate" \
  -H "Content-Type: application/json" \
  -d '{
    "credentialData": {
      "credentialConfigurationId": "eu.europa.ec.eudi.pid_mdoc.HR",
      "claims": {
        "eu.europa.ec.eudi.pid.1": {
          "birth_date": "1980-05-23",
          "expiry_date": "2026-05-23T12:12:12Z",
          "family_name": "Doe",
          "given_name": "John",
          "issuance_date": "2024-05-23T12:12:12Z",
          "issuing_authority": "HR",
          "issuing_country": "HR",
          "nationality": [
            "HR"
          ],
          "resident_city": "Zagreb",
          "resident_country": "HR",
          "resident_postal_code": "10000",
          "resident_street": "Street ABC",
          "sex": 5
        }
      }
    },
    "protocolConfig": {
      "grant_type": {
        "AuthorizationCode": {
          "issuer_state_payload": {
            "username": "test",
            "google": "test@example.com"
          }
        }
      }
    }
  }'

issuer_state_payload

In Authorization Code flow, the issuer can provide an optional issuer_state_payload object.

This payload:

  • is embedded into an issuer_state value
  • is forwarded by the wallet to the Authorization Server during authorization
  • can be used by the Authorization Server for additional checks and correlation
  • is expected by the issuer to appear within the access token presented later in the protocol flow

4. Complete the flow in the wallet (user authentication)

After initiation, you provide the Credential Offer (typically via a URI) to the user’s wallet, usually through:

  • a QR code
  • a deep link

The wallet then:

  1. Resolves the Credential Offer and discovers issuer metadata
  2. Detects that Authorization Code flow is required
  3. Redirects the user to the Authorization Server for login/consent
  4. Receives an authorization code and exchanges it for an access token
  5. Calls the issuer’s OID4VCI protocol endpoints using the access token
  6. Receives and stores the credential

The Issuer Service and wallet handle the protocol exchange automatically once the offer is delivered to the wallet.


5. Check issuance session status

While the wallet and issuer complete the issuance protocol, your system can query the issuance session status via the Control API.

curl -X POST "https://eis.fortid.com/control/issuer/mup-issuer/get-session-status/{sessionId}" \
  -H "Content-Type: application/json"

Status values

  • IN_PROGRESS — issuance is ongoing
  • ISSUANCE_FAILED — issuance failed (a description should explain why)
  • ISSUANCE_SUCCEEDED — credential was issued and received by the wallet

Expiration behavior

Issuance sessions time out after a configured duration:

  • After timeout, a grace period may apply during which the API returns 410 Session expired
  • After the grace period, the session is deleted and 404 Session not found is returned

On this page