# Understanding OpenID Connect (OIDC) for Persona Authentications

> The OIDC roles, tokens, and flow that Persona Authentications builds on, and how authentication templates and inquiries map onto them.

Source: https://help.withpersona.com/articles/WbPyh1Uk3ZwFpK86iohgVU/
Section: Authentications > Introduction > Key Concepts

Persona Authentications sits inside your sign-in flow as an identity provider, using [OpenID Connect (OIDC)](https://openid.net/specs/openid-connect-core-1_0.html) to talk to whatever sends users to it. This article covers the OIDC concepts the rest of the Authentications articles assume: the roles in the protocol, the tokens it issues, and how a Persona authentication template and inquiry line up with those concepts. For the setup steps themselves, see [How to configure Persona Authentications with OpenID Connect](https://help.withpersona.com/articles/1Acdx2QDfwgE1q3DTVPqW4/).

## The two roles in OIDC

OIDC is an identity layer on top of OAuth 2.0. It defines two parties:

- The **relying party (RP)**, the application that wants to know who a user is. This is your identity provider (IdP) or your own application, depending on how you have wired up your sign-in flow.
- The **OpenID provider (OP)**, the service that authenticates the user and issues tokens vouching for their identity.

Persona Authentications is the OpenID provider. Your IdP (or your application, if it talks to Persona directly) is the relying party.

## The authorization code flow

Persona implements the OIDC authorization code flow, which moves through three endpoints:

| Endpoint     | Purpose                                                                            |
| ------------ | ---------------------------------------------------------------------------------- |
| `/authorize` | Starts the flow. The relying party sends the user's browser here.                  |
| `/token`     | Exchanges a one-time authorization code for tokens, over a direct backend request. |
| `/userinfo`  | Returns account-backed claims for the authenticated user, filtered by scope.       |

At a protocol level, the flow looks like this:

1.  The relying party redirects the user's browser to Persona's `/authorize` endpoint with a `client_id`, `redirect_uri`, and the requested `scope`.
2.  Persona authenticates the user, in our case by running them through a verification inquiry, then redirects back to the relying party's `redirect_uri` with a one-time authorization `code`.
3.  The relying party exchanges that `code` at `/token` for an `id_token` and an `access_token`.
4.  The relying party can optionally call `/userinfo` with the `access_token` to fetch additional claims.

## `id_token` vs. `access_token`

| Token          | What it's for                                                                                                               |
| -------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `id_token`     | A signed JWT asserting who the user is. Read its claims directly; it's meant for the relying party, not for calling an API. |
| `access_token` | An opaque or signed credential the relying party presents to `/userinfo` to fetch more claims about the user.               |

## How Persona fills claims from Account fields

Scopes (like `openid`) and claims requests determine what shows up in each token. This section covers the claims that end up in the `id_token` when you request them.

Persona builds these claims from the **Account** tied to the authentication, not from the inquiry. If your flow defers account creation, the authentication can't resolve an Account; see [Deferring account creation for Persona Authentications](https://help.withpersona.com/articles/BIdQAkOaogc8WXT1TSv4r8/).

### Which Account fields map to which claims

The table lists the claims Persona sends by default, the scope that requests them, and the Account field each one comes from.

| Claim                   | Scope                | Account field it comes from                                                                                                                                                                                                                               |
| ----------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sub`                   | always present       | Defaults to the Account token. Can be mapped to another Account identifier instead, such as reference ID.                                                                                                                                                 |
| `name`                  | `profile`            | The Account's full name, assembled and normalized from the `name` field's first, middle, and last parts.                                                                                                                                                  |
| `given_name`            | `profile`            | `name.first`                                                                                                                                                                                                                                              |
| `family_name`           | `profile`            | `name.last`                                                                                                                                                                                                                                               |
| `middle_name`           | `profile`            | `name.middle`                                                                                                                                                                                                                                             |
| `nickname`              | `profile`            | `nickname`                                                                                                                                                                                                                                                |
| `preferred_username`    | `profile`            | Generally the `login_hint` sent by the relying party. Can be mapped to another Account identifier instead.                                                                                                                                                |
| `picture`               | `profile`            | A Persona-hosted file URL for the Account's selfie photo, when one is present.                                                                                                                                                                            |
| `birthdate`             | `profile`            | `birthdate`, formatted as `YYYY-MM-DD`.                                                                                                                                                                                                                   |
| `updated_at`            | `profile`            | When the Account was last updated, as a Unix timestamp.                                                                                                                                                                                                   |
| `email`                 | `email`              | `email_address`. Omitted when the Account has no email address.                                                                                                                                                                                           |
| `email_verified`        | `email`              | `email_verified`, defaulting to `false`. Omitted entirely when the Account has no email address.                                                                                                                                                          |
| `phone_number`          | `phone_number`       | `phone_number`. Omitted when the Account has no phone number.                                                                                                                                                                                             |
| `phone_number_verified` | `phone_number`       | `phone_number_verified`, defaulting to `false`. Omitted when the Account has no phone number.                                                                                                                                                             |
| `address`               | `address`            | The Account's `address` subfields as a nested OIDC address object: `formatted`, `street_address` (street lines joined by a newline), `locality` from city, `region` from subdivision, `postal_code`, and `country`. Omitted when every subfield is blank. |
| `verified_claims`       | `identity_assurance` | Built from the verified claims the relying party requested, returned in the `id_token` only. See [Using Verified Claims for Persona Authentications](https://help.withpersona.com/articles/eVRV0GeHbGkJCNHh1i9aQf/).                                                                  |

A claim whose Account field is blank is **omitted** from the token. Persona never returns a claim as `null` or as an empty string.

### Requesting claims

Claims can be requested in either the `id_token` or from the `/userinfo` endpoint with the OIDC `claims` request parameter, which names the individual claims you want.

They can also be requested with the `scopes` parameter, which is a shorthand for requesting groups of related claims:

| Scope                | Claims it requests                                                                                                            |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `openid`             | No claims. The ID token itself, which is required for an OIDC request.                                                        |
| `profile`            | `name`, `family_name`, `given_name`, `middle_name`, `nickname`, `preferred_username`, `picture`, `birthdate`, `updated_at`    |
| `email`              | `email`, `email_verified`                                                                                                     |
| `phone_number`       | `phone_number`, `phone_number_verified`                                                                                       |
| `address`            | `address`                                                                                                                     |
| `identity_assurance` | `verified_claims`                                                                                                             |
| `offline_access`     | No claims. Requests a refresh token in addition to the `id_token` and `access_token`.                                         |
| `tefca`              | TEFCA-specific claims for TEFCA integrations; see [TEFCA Individual Access Services (IAS)](https://help.withpersona.com/articles/ARLLoIOGw9P5nGlnC53uc/). |

Requesting a scope doesn't request all its claims. To keep the `id_token` compact, only `email`, `email_verified`, `name`, `given_name`, `family_name`, `preferred_username`, `picture`, and `verified_claims` are returned in the `id_token` when you request their scope. The `/userinfo` endpoint returns every supported claim except `verified_claims`, which the OIDC spec restricts to the `id_token`.

### Custom claims

Authentication template operators can define **custom claims** that put additional Account data into the tokens. A custom claim has a name, a value drawn from an Account field, and can be limited to particular scopes and to the `id_token`, the `/userinfo` endpoint, or both. Custom claims **supplement** the standard claims, and they never override one: if a custom claim shares a name with a standard claim, the standard value wins.

### Discovery documents and supported scopes

Persona publishes discovery documents at `https://authenticate.withpersona.com/authenticate/oidc/.well-known/openid-configuration` (production) and `https://authenticate.withpersona.com/authenticate/oidc-sandbox/.well-known/openid-configuration` (sandbox), and the matching issuer and token endpoints in [How to configure Persona Authentications with OpenID Connect](https://help.withpersona.com/articles/1Acdx2QDfwgE1q3DTVPqW4/). Discovery is published **per server**, not per authentication template, so `claims_supported` lists the union of every claim Persona can emit: a claim that appears there is not a promise that your template will emit it. What you receive for a given sign-in depends on the scopes and `claims` request, and on the Account's field values.

The scopes Persona supports today are `openid`, `profile`, `email`, `phone_number`, `address`, `identity_assurance`, `offline_access`, and `tefca`. The `tefca` scope covers TEFCA integration; see [TEFCA Individual Access Services (IAS)](https://help.withpersona.com/articles/ARLLoIOGw9P5nGlnC53uc/).

## How Persona's objects map onto OIDC

The protocol concepts above map onto three Persona objects:

- **Authentication template** is the OIDC client configuration: redirect URIs, scopes, the client authentication method, and any claim mappings. Every authentication template is bound to one inquiry template, which is what actually verifies the user.
- **Authentication** is a single run of that flow, one sign-in attempt by one user. It's the object that ties together the inquiry Persona ran and the tokens Persona issued.
- **Account** is where verified identity data ends up. When an authentication uses [verified claims](https://help.withpersona.com/articles/eVRV0GeHbGkJCNHh1i9aQf/), the ground-truth values you send land on the account tied to the authentication, not on the inquiry itself.

## What happens during a sign-in

1.  A user attempts to sign in to your application.
2.  Your IdP redirects them to Persona's `/authorize` endpoint (optionally after a [pushed authorization request](https://help.withpersona.com/articles/x9fUiSBIizAnfHL0o6a3SR/) to the IdP's backend).
3.  Persona finds or creates the account tied to the sign-in attempt, then runs the user through the inquiry bound to the authentication template.
4.  Once the inquiry completes, Persona redirects back to your IdP with an authorization code.
5.  Your IdP exchanges the code for tokens and lets the user into your application.

## Debugging a failed authentication

When an authentication fails, the error redirect includes a link back to that authentication in the Persona Dashboard, and the error message carries the authentication, inquiry, and account tokens. To get the same data programmatically instead of reading it off a redirect, subscribe to the `authentication.failed` webhook event.

## Where to go from here

- [How to configure Persona Authentications with OpenID Connect](https://help.withpersona.com/articles/1Acdx2QDfwgE1q3DTVPqW4/) walks through setting up an authentication template end to end.
- [Using Pushed Authorization Requests (PAR) for Persona Authentications](https://help.withpersona.com/articles/x9fUiSBIizAnfHL0o6a3SR/) covers sending identity data to Persona over a backend call instead of the browser.
- [Using Verified Claims for Persona Authentications](https://help.withpersona.com/articles/eVRV0GeHbGkJCNHh1i9aQf/) covers asking Persona to verify claims you already believe are true, including Okta's custom IDV integration.
- [Deferring account creation for Persona Authentications](https://help.withpersona.com/articles/BIdQAkOaogc8WXT1TSv4r8/) covers starting an authentication before Persona can identify which account it belongs to.

_Last updated on August 26, 2026._
