This is the core package written in pure dart, and maps the oidc spec to dart classes.
you can check the CLI example, showing how to use this package to implement the auth code flow in a CLI environment.
OidcUtils
getOpenIdConfigWellKnownUri
you can use this function to append .well-known/openid-configuration to any Uri
OidcReadOnlyStore + OidcStore
The abstract store implementation that needs to be implemented in order to have a persistent session.
we use this to store state, tokens, etc...
this was inspired by oidc-client-ts, but we have further improved this by adding the concept of namespaces.
So instead of having to maintain n stores, you only need one smart store, that is able to decide where to store things based on namespace.
OidcStoreNamespace
this is an enum that contains all the possible namespaces
session: Stores ephemeral information, such as the current state id and nonce.state: Stores states, this maps state id to state data.stateResponse: Stores unprocessed state responses, all the data stored in this namespace areUris that were the result of a redirect, which the app hasn't processed yet.request: Stores unprocessed requests from the openid provider (mainly frontchannel logout).discoveryDocument: caches discovery documents.secureTokens: stores sensitive tokens, likeaccess_tokenandid_token.
Info
On the web platform, since we use an external redirect.html page, implementations of the store MUST match the used page.
we made a default implementation in package:oidc_default_store and package:oidc_web_core, which matches the redirect.html page provided in our examples.
OidcMemoryStore
A simple implementation of OidcStore, used mainly on CLI apps and for testing.
It stores everything in memory, and doesn't persist anything.
OidcUserManagerBase
An abstract class containing all the base logic needed to implement oidc spec and maintain a user, regardless of platform.
Example implementations:
- CLI: example here
- Flutter:
OidcUserManagerin package:oidc - Dart Web:
OidcUserManagerWebin package:oidc_web_core
OidcEvent
The base class for all events, this contains an at property that stores when the event occurred.
OidcPreLogoutEvent
Occurs before a user is forgotten, either via forgetUser() or via logout().
OidcEndSessionConfirmationEvent
Occurs after an RP-initiated logout() when session management is enabled, the OP advertises a check_session_iframe and the ended session had a session_state. It carries what the OP's check_session_iframe answered for the ended session (OpenID Connect Session Management 1.0 §3.1): outcome is changed (the expected answer after a successful logout, but not proof of it: blocked third-party cookies can make the iframe answer changed for every call, §5.1, and unrelated sessions can cause false positives, §3.2), unchanged (the OP session is still alive, so the OP-side logout did not take; the only reliable signal), error, or timedOut (see OidcSessionManagementSettings.endSessionConfirmationTimeout). Logout does not wait for it, and the manager does not act on it. On a resumed web samePage logout it is emitted during init(), so subscribe to events() before calling init().
OidcPkcePair
you can use this to generate PKCE key pairs.
OidcConstants_*
A set of classes that map the oauth + openid connect constants to compile time constants for easier usage.
OidcToken
A serializable token.
This contains information about the access_token/id_token that was received from the /token endpoint.
Token properties are:
creationTime: when the token was created.scope: scopes that this token allows.accessToken: The issued access token. This is used to request resources from the server.tokenType: How to use the token. This is almost alwaysBearer, which indicates the use in the authorization headerAuthorization: Bearer {accessToken}.idToken: the issued id token that contains information about the user.expiresIn: the duration starting fromcreationTime, in which this token is considered valid.refreshToken: the issued refresh token. If available, we use this to request new access tokens once they expire.extra: extra values to extend the token.
The only thing required to create a token is its creationTime.
to create a token, you can use one of the following:
fromJson: to deserialize a token that was serialized usingtoJsonfromResponse: to create a token from a rawOidcTokenResponsethat you get from the/tokenendpoint.
There are also some useful methods that the token provides:
calculateExpiresAt: calculates the exact datetime for the token to expire. This is calculated ascreationTime + expiresIn.calculateExpiresInFromNow: calculates how much time left from now, for the token to expire, you can also overridenowandcreationTimeif you want. This is calculated asexpiresAt - now.isAccessTokenAboutToExpire: determine if the access token is about to expire, with an optionaltoleranceparameter (defaults to 1 minute). you can also override thenowandcreationTimeparameters.isAccessTokenExpired: determine if the access token has expired. This simply callsisAccessTokenAboutToExpirewithtolerance: Duration.zerotoJson: used to serialize the token into json.
OidcUser
A wrapper around OidcToken that requires the existence of id_token to understand information about the user; thus implementing the OIDC spec.
to create a user, you should call fromIdToken which takes the following parameters:
OidcToken token: the source token, this MUST containidToken.-
keystore: the store that contains information about the public json web keys. When provided, signature verification is always strict (fail-closed) — an id_token whose signature cannot be verified throws; there is no opt-out. When omitted, the id_token is parsed but left unverified.Key rotation
A
kidabsent from the currently-loaded JWKS triggers one rate-limited, cache-busting JWKS refetch before failing (OpenID Connect Core 1.0 §10.1.1), so a routine signing-key rotation at the OP does not cause a spurious verification failure.HS256 id_tokens
Some OPs (e.g. Auth0, for confidential clients by default) sign id_tokens with the symmetric
HS256algorithm, keyed by the client secret rather than a JWKS-published key. Verifying these requires anoctkey derived from the client secret to be present inkeystore—OidcUserManagerBase.init()does this automatically whenclientCredentialscarries aclientSecret. CallingfromIdTokendirectly (bypassing the manager) must add that key itself.attributes: extra attributes to put with the user for customization.userInfo: the response from the/userinfoendpoint.
Changing user properties
since the OidcUser is immutable, you need to create a new instance of it if you want to change its properties.
this is done using these functions:
withUserInfo: changes the response of the/userinfoendpoint.replaceToken: replaces the token that identifies the user with a new token, while leaving everything else. this is used in refresh token rotation to change the latest token the user has.setAttributes: merges input attributes with existing attributes.clearAttributes: removes all attributes.
When a response for a signed-in user has no id_token (typically a refresh, which OpenID Connect Core §12.2 allows),
replaceToken keeps the previous id_token and records that in the token: OidcToken.idTokenRetainedFromPriorResponse is
true. The flag is persisted with the token, and OidcToken.fromResponse never takes it from a server response.
ID token validation
OidcUserManagerBase.validateUser checks an id_token's claims and returns the list of problems it found. Which checks apply
can depend on the flow that produced the token, so validateUser, validateAndSaveUser and createUserFromToken take an
OidcIdTokenValidationContext:
const OidcIdTokenValidationContext({
OidcIdTokenSource source = OidcIdTokenSource.tokenEndpoint,
String? authorizationCode, // the code returned with the id_token, checked against c_hash
String? accessToken, // the access_token returned with the id_token, checked against at_hash
Duration? maxAge, // the max_age that was requested, checked against auth_time
});
The hash rules are judged from the response's own tokens. When a user is already signed in, the user built for a new
response keeps the previous session's access_token if the response returned none, so the access_token to check is passed in
the context. For authorizationEndpoint, accessToken: null means the response returned no access_token (id_token,
code id_token). For the other sources, null falls back to the user's own access_token. createUserFromToken fills
accessToken in from the response token it is given.
The at_hash and c_hash rules per OidcIdTokenSource:
| source | at_hash |
c_hash |
|---|---|---|
authorizationEndpoint (implicit, hybrid front channel) |
required when the response returned an access_token, and must match it | required when the response returned a code, and must match it |
tokenEndpoint (code exchange, password, device code) |
checked when present | checked when present |
refresh |
checked when present, only if the refresh returned a new id_token | same |
storedSession (init() revalidating the stored session) |
checked when present, unless the stored id_token was retained from an earlier response | same |
An id_token retained from an earlier response (idTokenRetainedFromPriorResponse) was not issued with the current tokens,
so its hashes are never required. A refresh, and the stored session it produced, do not compare them either (§12.2). Any
other response that kept the previous id_token, such as a password re-login whose response has no id_token, still
compares a hash the kept id_token carries, as before.
When validation fails
validateAndSaveUser returns null and clears the pending nonce. It does not touch the stored session or currentUser, so
a login or refresh response that fails validation leaves an already signed-in session in place.
A stored session that fails revalidation during init() is handled by the cached-token loader. It calls
OidcUserManagerSettings.shouldRemoveInvalidToken (by default the session is removed unless supportOfflineAuth is on). If
the policy keeps it, the token stays in the store. Either way no user is signed in with it: if init() had already signed a
user in with that session (by refreshing it, or by cache-first restoring it), that user is signed out again.
Migrating from 3.x
These are breaking changes for code that subclasses OidcUserManagerBase:
validateUser({user, metadata, authorizationCode, maxAge})is nowvalidateUser({user, metadata, context}).validateAndSaveUser({user, metadata, authorizationCode, maxAge, reactToUserInfoUnauthorized})is nowvalidateAndSaveUser({user, metadata, context, reactToUserInfoUnauthorized}).createUserFromToken({..., authorizationCode, maxAge, ...})is nowcreateUserFromToken({..., context, ...}).OidcIdTokenValidationContext.accessTokenis the access_token returned with the id_token. For theauthorizationEndpointsource,at_hashis checked against it and never against the validated user's token.
Pass the old arguments through the context:
// before
validateUser(user: user, metadata: metadata, authorizationCode: code, maxAge: maxAge);
// after
validateUser(
user: user,
metadata: metadata,
context: OidcIdTokenValidationContext(authorizationCode: code, maxAge: maxAge),
);
If you call validateUser or validateAndSaveUser for an id_token that came from the authorization endpoint, pass
source: OidcIdTokenSource.authorizationEndpoint and the access_token of that response as accessToken to get the
required-hash checks. Without accessToken, that source treats the response as having returned no access_token.
Behavior changes:
validateAndSaveUserno longer deletes the stored token, user info and attributes when validation fails. If your subclass relied on that, remove the session yourself (for example withforgetUser()).shouldRemoveInvalidTokenreturningfalsenow really keeps the stored token. Before, it had already been deleted.- An implicit
id_token tokenresponse whose id_token has noat_hashis now rejected (OpenID Connect Core §3.2.2.10). - A refresh that keeps the old id_token no longer fails the
at_hashcheck, either right away or when the app restarts. at_hashis compared with the access_token of the response that returned the id_token, not with one a signed-in user kept from an earlier response.- When
init()refreshes the stored session and the revalidation that follows fails, the refreshed user is now signed out even ifshouldRemoveInvalidTokenkeeps the session in the store.
Discovery document issuer validation
Per OIDC Discovery 1.0 §4.3 / RFC 8414 §3.3, OidcUserManagerSettings.strictIssuerValidation (default true) rejects a
discovery document whose issuer does not match the issuer it was expected to describe. A rejected document is neither
persisted nor kept: after a failed init() every flow (including getAccessToken(), which used to return null)
throws instead of building a request, and a rejected background (cache-first) refresh keeps the previously validated
document and still re-verifies the restored session against it. Set OidcUserManagerSettings.expectedIssuer when a provider's issuer cannot be derived
from the discovery URL; strictIssuerValidation: false only warns instead of rejecting, for providers expectedIssuer
cannot describe either.
Azure AD B2C
With B2C's default "Issuer (iss) claim" setting, the discovery issuer is not the discovery URL's prefix: the tenant
domain is replaced by the tenant GUID, the policy segment is dropped, and a trailing slash is added. For example,
https://fabrikamb2c.b2clogin.com/fabrikamb2c.onmicrosoft.com/B2C_1_susi/v2.0/.well-known/openid-configuration returns
"issuer": "https://fabrikamb2c.b2clogin.com/775527ff-9a37-4307-8b3d-cc311f58d925/v2.0/". See Microsoft's
token compatibility settings and,
for custom policies, IssuanceClaimPattern.
Options, most to least preferred:
- Switch the user flow/custom policy's "Issuer (iss) claim" (
IssuanceClaimPatternin custom policies) toAuthorityWithTfp, and use the matching/tfp/.../.well-known/openid-configurationdiscovery URL. The resulting issuer (https://<host>/tfp/<tenant-GUID>/<policy>/v2.0/) is exactly what that URL derives to, so noexpectedIssueris needed. This is Microsoft's documented option for OpenID Connect Discovery 1.0 compliance. The comparison is case-sensitive, so the discovery URL's tenant and policy segments must use the casing B2C emits in the issuer (otherwise setexpectedIssuer). - Keep B2C's default issuer format and set
expectedIssuerto the actual issuer B2C returns (as in the example above). - Last resort: set
strictIssuerValidation: false.
Microsoft Entra ID multi-tenant (/common, /organizations)
Entra's multi-tenant discovery issuer is a template (https://login.microsoftonline.com/{tenantid}/v2.0) rather than a
concrete URL. OidcUtils.discoveryIssuerMatches matches that template against an expectedIssuer pinned to a concrete
tenant (https://login.microsoftonline.com/<tenant>/v2.0), so pinning expectedIssuer is all a multi-tenant RP needs; see
#389.
Other Entra authorities
| Authority | Discovery issuer |
What to set |
|---|---|---|
Tenant by domain name, e.g. .../contoso.onmicrosoft.com/v2.0 |
https://login.microsoftonline.com/<tenant-GUID>/v2.0 |
Use the GUID authority, or expectedIssuer = that issuer |
.../consumers/v2.0 |
https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0 (concrete, not a template) |
expectedIssuer = that issuer |
v1 https://login.microsoftonline.com/common |
https://sts.windows.net/{tenantid}/ |
expectedIssuer = https://sts.windows.net/<tenant-GUID>/ |
OidcException
Most of the errors thrown by this library are of type OidcException.
it contains the following properties:
message: a message that describes the error.errorResponse: the error response coming from the auth server, if it exists.internalExceptionandinternalStackTrace, if this error contains other internal errors.extra: some extra parameters that describe the error, this can contain the rawRequest/Responseobjects frompackage:http.
OidcTokenEventsManager
Manages token events.
you can load a token, and the watch its events in the expiring/expired streams.
OidcEndpoints
Contains methods that help you implement the OIDC spec yourself.
prepareAuthorizationCodeFlowRequest: this is used to prepare an opinionated authorization code flow request, by creating a PKCE pair and a state parameter.prepareImplicitFlowRequest: this is used to prepare an opinionated implicit flow request, by creating a state parameter.getProviderMetadata: gets and parses provider metadata from the authorization server's well-known endpoint.parseAuthorizeResponse: parses the uri that you get from the authorization flow, and returns useful information.token: sends a request to the/tokenendpoint.userInfo: sends a request to the/userinfoendpoint.deviceAuthorization: sends a request to the device authorization endpoint.