OIDC Backend flow Authentication

The OIDC Authorization Client-Initiated Backchannel Authentication (CIBA) flow is a secure way to obtain an access token from the client backend.

Depending on the API two distinct methods are possible:

  • Basic Auth: The client_id and client_secret are sent in the Authorization header using the Basic Auth scheme.
  • JWT assertion: A signed JWT is sent in the request body with the client_assertion and client_assertion_type parameters. The JWT is signed with the client private key and contains the client_id as a parameter allowing the authentication. You can find more information here in this document.

The base URL of the authorization server is provided in the description of each API.

The following steps describe how to implement the OIDC CIBA flow to obtain an access token.

Step 1: Retrieve the Authorization code

In both cases (Basic Auth or JWT assertion) the client backend send an HTTP POST request to the authorization server with the following parameters:

  • login_hint: The end-user identifier (email or phone number, e.g. tel:+33712345678)
  • scope: The scope for which the access token is requested with the value openid dpv:<dpvValue> <technicalParameter>. Orange implementation follows the CAMARA scope definition.
    • dpv stands for Data Privacy Vocabulary, for example FraudPreventionAndDetection
    • technical parameters are the data claims by the API consumer, for example sim-swap:check The values to use are precised in the documentation of each API.

Basic Auth authorization code request

For APIs which support the Basic Auth authentication, the Authorization HTTP header must be populated with Basic base64_encode(client_id:client_secret).

Here is a sample request:

curl -X POST '<authorization server base url>/bc-authorize' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  -u '<your client id>:<your client secret>' \
  --data-urlencode 'scope=openid dpv:<dpvValue> <technicalParameter>' \
  --data-urlencode 'login_hint=tel:<end-user phone number>'

JWT assertion based code request

For APIs which support the JWT assertion based authentication, the following parameters must be provided:

  • client_assertion: The signed JWT token
  • client_assertion_type: The assertion type which must be set to urn:ietf:params:oauth:client-assertion-type:jwt-bearer

Here is a sample request:

curl -X POST \
  '<authorization server base url>/bc-authorize' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'scope=openid dpv:<dpvValue> <technicalParameter>' \
  --data-urlencode 'login_hint=tel:<end-user phone number>' \
  --data-urlencode 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \
  --data-urlencode 'client_assertion=<signed jwt>'

Handling the response

If the request is successful the JSON response contains the following fileds:

  • auth_req_id: The authentication request identifier to use in the next step.
  • expires_in: The expiration time, in seconds, of the authentication request identifier.
  • interval: The interval, in seconds, to use to poll the authentication request status.

Here is a sample response:

{
  "auth_req_id": "3f7b2e8a-...-1a2b3c4d5e6f",
  "expires_in": 120,
  "interval": 2
}

Step 2: Retrieve the access token

Once the client backend application got the authorization code, it has to get the access token protecting the resources. In order to retrieve it, the client application triggers a POST request to the token endpoint.

This endpoint is authenticated and the API consumer must authenticate itself using either the Basic Auth scheme or the JWT assertion one.

In both cases the following parameters must be populated:

  • grant_type: The value of this parameter must be urn:openid:params:grant-type:ciba.
  • auth_req_id: The authorization request id received during the previous step from the authorization server.

Basic Auth Access token request parameters

For API which support the Basic Auth authentication, the Authorization HTTP header must be populated with Basic base64_encode(client_id:client_secret).

Here is a sample request:

curl -X POST '<authorization server base url>/token' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  -u '<your client id>:<your client secret>' \
  --data-urlencode 'grant_type=urn:openid:params:grant-type:ciba' \
  --data-urlencode 'auth_req_id=<auth_req_id obtained from previous step>'

JWT assertion based code request

For API which support the JWT assertion based authentication, the following parameters must be provided:

  • client_assertion: The signed JWT token
  • client_assertion_type: The assertion type which must be set to urn:ietf:params:oauth:client-assertion-type:jwt-bearer

Here is a sample request:

curl -X POST \
  '<authorization server base url>/token' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=urn:openid:params:grant-type:ciba' \
  --data-urlencode 'auth_req_id=<auth_req_id obtained from previous step>' \
  --data-urlencode 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \
  --data-urlencode 'client_assertion=<signed jwt>'

Handling the response

If the request is successful the JSON response contains the following fileds:

  • access_token: The token you will use to access the API resources.
  • token_type: The type of the token (e.g. Bearer).
  • expires_in: The validity time of the token in secondes.

Here is a sample response:

{
  "access_token": "OFR_28FpNfX...7lwFAg3LR0vmHCOmJAg7Si",
  "token_type": "Bearer",
  "expires_in": 3600
}

JWT assertion in a nutshell

OpenID Connect (OIDC) can utilize JWT assertions for client authentication, especially in scenarios where traditional client secrets are not suitable. JWT assertions are signed tokens that assert the client's identity to the authorization server.

For more details, refer to the OpenID Connect Core.

JWT assertion creation

Construct a JWT with the following claims:

  • iss (issuer): your client ID → It's the only field that is used by the Authorization server to identify the issuer of the jwt and retrieve the associated public key.
  • sub (subject): no constraint on this field
  • aud (audience): no constraint on this field
  • iat (issued at): current timestamp
  • exp (expiration): timestamp after a short period (e.g., 5 minutes)

Sign the JWT with your private key using RS256 or another suitable algorithm.

import jwt
import time

private_key = """-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----"""

now = int(time.time())

jwt_assertion = jwt.encode(
    {
        "iss": "YOUR_CLIENT_ID",
        "sub": "YOUR_CLIENT_ID",
        "aud": "https://authorization-server.com/token",
        "iat": now,
        "exp": now + 300
    },
    private_key,
    algorithm="RS256"
)

JWKS and JWK signature verification

JWKS (JSON Web Key Set) and JWK (JSON Web Key) are standards for representing cryptographic keys in JSON format, commonly used for verifying JWT signatures in OpenID Connect and OAuth 2.0.

For more details, see RFC 7517 - JSON Web Key (JWK) and OpenID Connect Discovery.

Usage Steps

1. Retrieve the JWKS

The authorization server exposes a JWKS endpoint, typically at:

https://authorization-server.com/.well-known/jwks.json

You can fetch it with curl:

curl https://authorization-server.com/.well-known/jwks.json
2. Parse the JWKS

The JWKS contains an array of JWKs, each representing a key with parameters like kty, kid, n, e, etc.

Example:

{
  "keys": [
    {
      "kty": "RSA",
      "kid": "abc123",
      "use": "sig",
      "n": "base64url-modulus",
      "e": "AQAB"
    }
  ]
}
3. Verify a JWT using JWK

Use a JWT library (e.g., pyjwt in Python) to verify the token signature with the appropriate key from the JWKS.

import requests
import jwt

# Fetch JWKS
jwks_url = "https://authorization-server.com/.well-known/jwks.json"
jwks = requests.get(jwks_url).json()

# Select the key with matching kid
kid = jwt.get_unverified_header(token)['kid']
key = next(k for k in jwks['keys'] if k['kid'] == kid)

# Construct public key
public_key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(key))

# Verify token
decoded = jwt.decode(token, public_key, algorithms=['RS256'], audience='YOUR_CLIENT_ID')
4. Use JWK for key management

JWKs can also be used to generate or rotate keys, and to distribute public keys securely.

JWKS generation

1. Generate a cryptographic key

Depending on the key type (kty), generate the key:

RSA key (using openssl)
# Generate RSA private key
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048

# Extract public key in PEM format
openssl rsa -pubout -in private_key.pem -out public_key.pem

2. Convert the RSA public key to JWK

Use a tool or library to convert the public key to JWK format. For example, with sshpk (Node.js) or jwks-rsa libraries.

Alternatively, use pem-jwk CLI:

# Install pem-jwk
npm install -g pem-jwk

# Convert PEM to JWK
pem-jwk public_key.pem > jwk.json

3. Create a JWKS

Wrap the JWK in a JWKS object:

{
  "keys": [
    {
      "kty": "RSA",
      "kid": "unique-key-id",
      "use": "sig",
      "n": "base64url-modulus",
      "e": "AQAB"
    }
  ]
}

You can generate the kid (key ID) as a unique identifier, e.g., a UUID or hash.

4. Automate with libraries (Python example)

Using cryptography and jwcrypto libraries:

from jwcrypto import jwk
import json

# Generate RSA key
key = jwk.JWK.generate(kty='RSA', size=2048)

# Export public key as JWK
public_jwk = key.export_public()

# Create JWKS
jwks = {
    "keys": [json.loads(public_jwk)]
}

# Save JWKS
with open('jwks.json', 'w') as f:
    json.dump(jwks, f)