# Credential Offer
This module implements the **User Request Flow** for Issuer-Initiated credential issuance, as defined in [IT-Wallet Technical Specifications v1.3.3, Section 12.1.2](https://italia.github.io/eid-wallet-it-docs/) and [OpenID for Verifiable Credential Issuance 1.0, Section 4.1](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-offer-endpoint).
The flow processes a Credential Offer received from a Credential Issuer (or a Third Party / Authentic Source) and extracts the grant details needed to start the Issuance Flow.
All operations delegate to the [`@pagopa/io-wallet-oid4vci`](https://github.com/nicolo-ribaudo/io-wallet-oid4vci) SDK.
## Flow overview
```mermaid
graph TD;
QR[QR code / deep link]
1[resolveCredentialOffer]
2[extractGrantDetails]
Issuance[Issuance Flow]
QR --> 1
1 -->|CredentialOffer| 2
2 -->|ExtractGrantDetailsResult| Issuance
```
| Step | Function | What it does |
|------|----------|--------------|
| 1 | `resolveCredentialOffer` | Parses the URI, fetches the offer if by-reference, validates structure |
| 2 | `extractGrantDetails` | Extracts the `authorization_code` grant details from the resolved offer |
## Credential Offer transmission
A Credential Offer can reach the Wallet Instance in two ways (OpenID4VCI Section 4.1):
### By value (`credential_offer`)
The complete Credential Offer JSON is embedded in the URI as an encoded query parameter:
```
openid-credential-offer://?credential_offer=%7B%22credential_issuer%22...%7D
```
### By reference (`credential_offer_uri`)
A URL points to a resource serving the Credential Offer. The Wallet fetches it via HTTP GET with `Accept: application/json`. The Credential Issuer or Third Party SHOULD use a unique URI per offer to prevent caching.
```
openid-credential-offer://?credential_offer_uri=https%3A%2F%2Fissuer.example.com%2Foffer
```
Both modes are handled transparently by `resolveCredentialOffer` (via the SDK).
## Supported URI schemes
The QR code or deep link must use one of the following schemes:
- `openid-credential-offer://` (OpenID4VCI Section 4)
- `haip-vci://` (OPENID4VC-HAIP Section 4.2)
- `https://` (Universal Link, if listed in the Wallet's `credential_offer_endpoint`)
## Step 1 — Resolve and validate
`resolveCredentialOffer(uri, { fetch })` performs two operations:
1. **Resolution** — parses the URI and, if the offer is by-reference, fetches the JSON from the remote endpoint.
2. **Structural validation** — checks the resolved offer against IT-Wallet v1.3 rules:
- `credential_issuer` must be an HTTPS URL
- `grants` object is required
- `authorization_code` grant is required
- `scope` is required within `authorization_code`
> **Note:** cross-validation against Credential Issuer metadata (e.g. verifying `credential_configuration_ids` against `credential_configurations_supported`, or matching `authorization_server` against the metadata's `authorization_servers`) is **not** part of this step. Per the spec, metadata processing happens once the User Request Flow is completed, at the start of the Issuance Flow.
## Step 2 — Extract grant details
`extractGrantDetails(offer)` reads the `grants` object and returns an `ExtractGrantDetailsResult`:
```ts
{
grantType: "authorization_code",
authorizationCodeGrant: {
scope: string, // REQUIRED — used in the Authorization Request
issuerState?: string, // binds the request to the Credential Issuer session
authorizationServer?: string // REQUIRED when the issuer uses multiple AS
}
}
```
IT-Wallet v1.3 only supports the `authorization_code` grant type.
## Credential Offer parameters
Reference: IT-Wallet spec, Section 12.1.2, Credential Offer parameters table.
| Field | Required | Description |
|-------|----------|-------------|
| `credential_issuer` | REQUIRED | HTTPS URL that uniquely identifies the Credential Issuer. Used to discover its metadata. |
| `credential_configuration_ids` | REQUIRED | Array of credential type identifiers. Each must match an entry in the Issuer's `credential_configurations_supported`. |
| `grants.authorization_code.scope` | REQUIRED | Maps to a specific credential type. The Wallet MUST use this value in the Authorization Request. |
| `grants.authorization_code.issuer_state` | OPTIONAL | Opaque string from the Issuer. When present the Wallet MUST include it in the Authorization Request. |
| `grants.authorization_code.authorization_server` | CONDITIONAL | REQUIRED when the Issuer uses more than one Authorization Server. Must match one entry in the Issuer's `authorization_servers` metadata. |
## Error mapping
| Error | Code | When |
|-------|------|------|
| `InvalidQRCodeError` | `ERR_INVALID_QR_CODE` | URI parsing fails (unsupported scheme, missing params, invalid or malformed query) |
| `InvalidCredentialOfferError` | `ERR_INVALID_CREDENTIAL_OFFER` | Structural validation fails (missing grant, missing scope, non-HTTPS issuer) or grant extraction fails |
> Note: Network or other unexpected errors thrown while fetching an offer by reference in `resolveCredentialOffer` are propagated as-is and are not mapped to `InvalidQRCodeError` or `InvalidCredentialOfferError`.
## Boundary with the Issuance Flow
The Credential Offer flow ends once `extractGrantDetails` returns. The Issuance Flow then begins with:
1. Credential Issuer metadata processing (Trust Evaluation / Federation check)
2. Cross-validation of the offer against metadata
3. PAR Request, Authorization, Token exchange, Credential Request
## Examples
Offer by reference
```ts
import { ioWallet } from "@pagopa/io-react-native-wallet";
const wallet = new IoWallet({ version: "1.4.6" });
const uri =
"openid-credential-offer://?credential_offer_uri=https%3A%2F%2Fissuer.example.com%2Foffer";
// 1) Resolve — fetches the offer from the URI and validates it
const offer = await wallet.CredentialsOffer.resolveCredentialOffer(uri, {
fetch: appFetch,
});
// 2) Extract grant details
const grant = wallet.CredentialsOffer.extractGrantDetails(offer);
// {
// grantType: "authorization_code",
// authorizationCodeGrant: { scope: "org.iso.18013.5.1.mDL", ... }
// }
```
Offer by value
```ts
import { ioWallet } from "@pagopa/io-react-native-wallet";
const wallet = new IoWallet({ version: "1.4.6" });
const uri =
"openid-credential-offer://?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fissuer.example.com%22%2C%22credential_configuration_ids%22%3A%5B%22UniversityDegree%22%5D%2C%22grants%22%3A%7B%22authorization_code%22%3A%7B%22scope%22%3A%22UniversityDegree%22%7D%7D%7D";
// 1) Resolve — decodes the inline offer and validates it
const offer = await wallet.CredentialsOffer.resolveCredentialOffer(uri);
// 2) Extract grant details
const grant = wallet.CredentialsOffer.extractGrantDetails(offer);
```