Identity API (OIDC)
Standards-based OpenID Connect on top of OAuth 2.0. Let business customers sign in to your application with their Credicorp account and read a verified identity. Discovery, JWKS and the PKCE authorization-code flow are fully conformant — any certified OIDC library works out of the box.
Credicorp is the OpenID Provider (OP); your application is a Relying Party (RP). Sign-in happens at sso.credicorp.co.uk, the token and userinfo endpoints sit on the public ring, and signing keys are published at the standard JWKS URL. Because the provider is conformant, you should not hand-roll the flow — use a certified client (we ship one in each SDK) and let it validate the ID token for you.
- Issuer
https://sso.credicorp.co.uk- Discovery
/.well-known/openid-configuration- Flow
- Authorization Code + PKCE (RFC 7636). Implicit flow is not supported.
- ID token
- JWT signed
ES256; verify against the published JWKS.
Discovery
Everything an RP needs is advertised at the discovery document. Fetch it once at start-up and cache it; never hard-code endpoint URLs.
{
"issuer": "https://sso.credicorp.co.uk",
"authorization_endpoint": "https://sso.credicorp.co.uk/authorize",
"token_endpoint": "https://sso.credicorp.co.uk/token",
"userinfo_endpoint": "https://sso.credicorp.co.uk/userinfo",
"jwks_uri": "https://sso.credicorp.co.uk/.well-known/jwks.json",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"id_token_signing_alg_values_supported": ["ES256"],
"code_challenge_methods_supported": ["S256"],
"scopes_supported": ["openid", "profile", "email", "business", "accounts.read"]
}Endpoints
| Method | Path | Purpose |
|---|---|---|
| GET | /authorize | Begin sign-in; redirects the user to authenticate & consent. |
| POST | /token | Exchange the authorization code (+ PKCE verifier) for tokens. |
| GET | /userinfo | Return claims for the authenticated subject (bearer the access token). |
| GET | /.well-known/jwks.json | Public keys for verifying ID-token signatures. |
| GET | /.well-known/openid-configuration | Provider metadata (discovery). |
The PKCE authorization-code flow
Use Authorization Code with PKCE for every client type — web servers, SPAs and native apps alike. The four steps:
- Generate a random
code_verifierand its SHA-256code_challenge. Store the verifier in the session. - Redirect the user to
/authorizewith the challenge and a uniquestate. - Credicorp authenticates the user, they consent, and we redirect back to your
redirect_uriwith a one-timecode. - Your server POSTs the
code+code_verifierto/tokenand receives anid_token,access_tokenandrefresh_token.
Step 1 — Build the challenge
# code_verifier: 43–128 chars of [A-Za-z0-9-._~] verifier=$(openssl rand -base64 32 | tr -d '=+/' | cut -c -43) challenge=$(printf "%s" "$verifier" | openssl dgst -sha256 -binary \ | openssl base64 -A | tr '+/' '-_' | tr -d '=')
Step 2 — Redirect to /authorize
https://sso.credicorp.co.uk/authorize ?response_type=code &client_id=rp_acme_live_2P &redirect_uri=https://app.acme.co.uk/auth/callback &scope=openid profile email business &state=x7Kp… &code_challenge=E9Melhoa2Ow… &code_challenge_method=S256
/authorize parameters
| Parameter | Description | |
|---|---|---|
response_type | req | Always code. |
client_id | req | Your registered RP client id. |
redirect_uri | req | Must exactly match a URI registered for the client. |
scope | req | Space-delimited; must include openid. |
state | req | Opaque anti-CSRF value; echoed back on the callback. |
code_challenge | req | Base64url SHA-256 of your verifier. |
code_challenge_method | req | Always S256. |
nonce | opt | Bound into the ID token to prevent replay. |
prompt | opt | login to force re-auth, none for silent SSO. |
Step 3 & 4 — Exchange the code at /token
curl -s https://sso.credicorp.co.uk/token \ -d "grant_type=authorization_code" \ -d "code=ac_91Hb2…" \ -d "redirect_uri=https://app.acme.co.uk/auth/callback" \ -d "client_id=rp_acme_live_2P" \ -d "code_verifier=$verifier"
import { Identity } from '@credicorp/sdk'; const oidc = await Identity.discover(); // reads discovery + JWKS const client = new oidc.Client({ client_id: 'rp_acme_live_2P', redirect_uri: 'https://app.acme.co.uk/auth/callback' }); const tokens = await client.callback(req.query, { state: req.session.state, code_verifier: req.session.verifier }); // tokens.claims() → verified ID-token claims
use Credicorp\Identity; $oidc = Identity::discover(); $client = $oidc->client( clientId: 'rp_acme_live_2P', redirectUri: 'https://app.acme.co.uk/auth/callback', ); $tokens = $client->callback($_GET, [ 'state' => $_SESSION['state'], 'code_verifier' => $_SESSION['verifier'], ]); $claims = $tokens->claims(); // signature already verified
Token response 200 OK
{
"token_type": "Bearer",
"expires_in": 3600,
"access_token": "at_8sV…",
"refresh_token": "rt_K2p…",
"id_token": "eyJhbGciOiJFUzI1NiIs…",
"scope": "openid profile email business"
}Always validate the ID token. Verify the ES256 signature against the JWKS, and check iss equals the issuer, aud equals your client_id, exp is in the future, and nonce matches what you sent. The SDK does all of this for you — do not parse the JWT yourself.
Scopes & claims
Request the minimum scope you need. Identity claims are returned in the ID token and, in full, from /userinfo.
| Scope | Grants |
|---|---|
openid | Required. Returns sub — the stable subject identifier. |
profile | name, given_name, family_name, updated_at. |
email | email and email_verified. |
business | Verified company identity: company_number, company_name, company_role. |
accounts.read | Lets the access token call the Accounts API on the user's behalf. |
/userinfo response
curl https://sso.credicorp.co.uk/userinfo -H "Authorization: Bearer at_8sV…" { "sub": "usr_2WdR7yK", "name": "Priya Anand", "email": "[email protected]", "email_verified": true, "company_number": "09876543", "company_name": "Acme Trading Ltd", "company_role": "director" }
Key the user on sub, not email. A person's email can change; sub is the permanent, opaque identifier for that Credicorp account and is what you should store against your own user record.
Refreshing & sign-out
Access tokens live for one hour. Exchange the refresh token at /token with grant_type=refresh_token to obtain a fresh access token without re-prompting. To end a session, clear your own cookie and redirect the user to the end-session endpoint advertised in discovery. Refresh tokens rotate on use — always persist the newest one returned.
| error | HTTP | Cause |
|---|---|---|
invalid_grant | 400 | Code expired, already used, or verifier mismatch. |
invalid_client | 401 | Unknown client_id or disabled RP. |
access_denied | 403 | User declined consent at /authorize. |
Related: OAuth 2.0 for the underlying grant model, Accounts for what an authorised user can read, and Errors for the standard error/error_description shape returned by the token endpoint.
