# Token Request

The token endpoint exchanges an authorization code for an ID token containing verified identity claims.

## Endpoint

```
POST /token
```

## Request Format

### Headers

```http
Content-Type: application/x-www-form-urlencoded
```

### Parameters

| Parameter               | Required | Type   | Description                                                      |
| ----------------------- | -------- | ------ | ---------------------------------------------------------------- |
| `client_id`             | Yes      | string | Your OAuth client identifier                                     |
| `client_assertion_type` | Yes      | string | Must be `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` |
| `client_assertion`      | Yes      | string | Signed JWT for client authentication                             |
| `grant_type`            | Yes      | string | Must be `authorization_code`                                     |
| `code`                  | Yes      | string | Authorization code from callback                                 |
| `code_verifier`         | Yes      | string | PKCE code verifier (43-128 characters)                           |
| `redirect_uri`          | Yes      | string | Must match the redirect_uri from PAR                             |

### Example Request

```http
POST /token HTTP/1.1
Host: {dip-base-url}
Content-Type: application/x-www-form-urlencoded

client_id=dip_aci_your_client_id
&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer
&client_assertion=eyJhbGciOiJFUzI1NiIsImtpZCI6Im15LWtleSIsInR5cCI6IkpXVCJ9...
&grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
&redirect_uri=https://client.example.org/callback
```

## Client Assertion JWT

The client assertion authenticates your client. See [Authentication](https://docs.stoe.no/id-check/getting-started/authentication.md) for full details.

**Header (3 claims exactly):**

```json
{
  "alg": "ES256",
  "kid": "my-signing-key",
  "typ": "JWT"
}
```

**Payload (4 required claims):**

```json
{
  "iss": "dip_aci_your_client_id",
  "sub": "dip_aci_your_client_id",
  "aud": "https://{dip-base-url}",
  "exp": 1759835872
}
```

> **Note:** Standard JWT claims `iat`, `jti`, and `nbf` are accepted but not required. Any other claims will be rejected.

## Response

### Success Response (200 OK)

#### Response Headers

| Header          | Value              |
| --------------- | ------------------ |
| `Content-Type`  | `application/json` |
| `Cache-Control` | `no-store`         |
| `Pragma`        | `no-cache`         |

#### Response Body

```json
{
  "id_token": "eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIiwia2lkIjoiZGlwLWVuYy1rZXkifQ...",
  "access_token": "not_applicable",
  "token_type": "Bearer",
  "expires_in": 900
}
```

| Field          | Type   | Description                                                                                            |
| -------------- | ------ | ------------------------------------------------------------------------------------------------------ |
| `id_token`     | string | Encrypted JWT (JWE) containing the signed ID token                                                     |
| `access_token` | string | Placeholder value. DIP is an identity verification service where the ID token is the primary artifact. |
| `token_type`   | string | OAuth 2.0 token type. Always `Bearer`.                                                                 |
| `expires_in`   | number | Token lifetime in seconds                                                                              |

### Error Response

```json
{
  "error": "invalid_request",
  "error_description": "Session expired"
}
```

### Error Codes

| Error             | HTTP Status | Description                                                                                     |
| ----------------- | ----------- | ----------------------------------------------------------------------------------------------- |
| `invalid_request` | 400         | Missing or invalid parameters, expired session, redirect URI mismatch, or invalid session state |
| `invalid_client`  | 401         | Client authentication failed (unknown client or invalid credentials)                            |
| `not_found`       | 404         | Session not found for the given authorization code                                              |
| `access_denied`   | 404         | Unauthorized session access or PKCE verification failure                                        |
| `server_error`    | 500         | Internal server error                                                                           |

## ID Token Format

The `id_token` is a **JWE (JSON Web Encryption)** containing a **signed JWT**.

### Encryption Details

| Property   | Value               |
| ---------- | ------------------- |
| Algorithm  | `RSA-OAEP-256`      |
| Encryption | `A256GCM`           |
| Content    | Signed JWT (nested) |

### Decryption Process

1. Parse the JWE
2. Decrypt using your private key
3. Extract the signed JWT from the payload
4. Verify the JWT signature using DIP's public key (from `/jwks`)
5. Validate claims

### Signed JWT Details

| Property  | Value   |
| --------- | ------- |
| Algorithm | `ES256` |
| Type      | `jwt`   |

## Complete ID Token Structure

After decryption and signature verification, the ID token contains:

```json
{
  "iss": "https://{dip-base-url}",
  "sub": "hashed-pairwise-subject-identifier",
  "aud": "dip_aci_your_client_id",
  "exp": 1759839472,
  "iat": 1759835872,
  "auth_time": 1759835872,
  "nonce": "n-0S6_WzA2Mj",
  "acr": "urn:bankid:idcheck",
  "amr": ["face", "user"],
  "verified_claims": {
    "verification": {
      "trust_framework": "stoe",
      "evidence": [
        {
          "type": "document",
          "check_details": [
            {
              "check_method": "vcrypt",
              "result": "success"
            },
            {
              "check_method": "data",
              "organization": "id.politiet.no",
              "result": "valid"
            }
          ],
          "document_details": {
            "type": "passport",
            "document_number": "AB1234567",
            "date_of_issuance": "2020-01-15",
            "date_of_expiry": "2030-01-15",
            "issuer": {
              "country_code": "NOR",
              "name": "INNLANDET POLITIDISTRIKT"
            },
            "personal_number": "12345678901"
          }
        }
      ]
    },
    "claims": {
      "given_name": "AASAMUND SPECIMEN",
      "family_name": "OESTENBYEN",
      "birthdate": "1990-01-15",
      "gender": "male",
      "nationalities": ["NOR"]
    }
  }
}
```

See [ID Token](https://docs.stoe.no/id-check/id-token.md) for complete claim documentation.

## Validation Checklist

After decrypting the ID token, validate the following:

| Claim     | Validation                                 |
| --------- | ------------------------------------------ |
| `iss`     | Must match DIP issuer URL                  |
| `aud`     | Must match your `client_id`                |
| `exp`     | Must not be expired (current time < exp)   |
| `iat`     | Must not be in the future                  |
| `nonce`   | Must match the nonce from your PAR request |
| `acr`     | Should be `urn:bankid:idcheck`             |
| Signature | Must be valid using DIP's public key       |

## Next Steps

- [ID Token](https://docs.stoe.no/id-check/id-token.md) - Complete ID token structure and claim details
- [Authentication](https://docs.stoe.no/id-check/getting-started/authentication.md) - JWT requirements
- [API Reference](https://docs.stoe.no/id-check/api/openid-configuration.md) - OpenID discovery endpoint
