Using the plugin
all the classes that are exposed by this plugin start with Oidc*
.
OidcUserManager
Construction
The main class this plugin provides, as the name suggests, it manages the current user session.
NOTE: you should only maintain a single instance of this class in your app.
you have two ways to construct it, depending on the availability of the Discovery Document.
- If you have the discovery document already:
final manager = OidcUserManager( discoveryDocument: OidcProviderMetadata.fromJson({ 'issuer': 'https://server.example.com', 'authorization_endpoint': 'https://server.example.com/connect/authorize', 'token_endpoint': 'https://server.example.com/connect/token', //...other metadata }), //...other parameters. );
- If you don't have the discovery document
aside from the discovery document, both constructors share the same parameters:
clientCredentials
an OidcClientAuthentication
describes how the client authenticates with the identity provider, which has the following constructors:
.none
: good for public clients, only theclientId
is required..clientSecretBasic
: good for confidential clients that can keep a secret. this uses the HTTP Basic authentication scheme..clientSecretPost
: same asclientSecretBasic
, but passes theclientId
andclientSecret
as form parameters, instead ofAuthorization
header..clientSecretJwt
: you create a JWT based on theclientId
andclientSecret
, instead of passing the secret directly..privateKeyJwt
: similar toclientSecretJwt
, but you first have to create an Asymmetric key pair, and sign the JWT using the public key.
how you create/obtain the jwt is outside the scope of this package. you can use to help you.
store
an instance of OidcStore
, we provide 3 types of stores out of the box, depending on your use case:
OidcMemoryStore
from package:oidc_core; which stores the auth state in memory (good for CLI apps or during testing).OidcDefaultStore
from package:oidc_default_store; which persists the auth state on disk orlocalStorage
on web, And tries to encrypt the data if possible.OidcWebStore
from package:oidc_web_core; which persists the auth state onlocalStorage
/session_storage
on web.
settings
settings to control the behavior of the instance.
-
bool supportOfflineAuth = false
: whether the app should keep expired tokens if it's not able to contact the server.Warning
Enabling offline auth can be a security risk, as it allows the app to keep tokens that are no longer valid. And it may open up unexpected attack vectors.
-
bool strictJwtVerification = false
: whether JWTs are strictly verified. Uri redirectUri
: the redirect uri that was configured with the provider.Uri? postLogoutRedirectUri
: the post logout redirect uri that was configured with the provider.Uri? frontChannelLogoutUri
: the uri of the front channel logout flow. this Uri MUST be registered with the OP first. the OP will call this Uri when it wants to logout the user.-
frontChannelRequestListeningOptions
: the options to use when listening for front channel logout requests.frontChannelRequestListeningOptions: OidcFrontChannelRequestListeningOptions( //currently only supports web. web: OidcFrontChannelRequestListeningOptions_Web( broadcastChannel: 'oidc_flutter_web/request' ) )
Note that this channel needs to be the same as the one in your redirect.html page.
-
Duration expiryTolerance
: also known as clock skew, it's a small duration that lengthens the token expiry time, to account for communication delays; default is 1 minute. -
Duration? Function(OidcToken token)? refreshBefore
: a function that controls how you want the automatic refresh_token handling to work. you receive a token, and you decide for it how early the token gets refreshed. for example:- if
Duration.zero
is returned, the token gets refreshed once it's expired. - if
null
is returned, automatic refresh is disabled.
by default, this is a function that returns
Duration(minutes: 1)
for every token, which means that all tokens are refreshed 1 minute before they expire. - if
-
Duration? Function(OidcTokenResponse tokenResponse)? getExpiresIn
: a function that overrides a token's expires_in value, it's not recommended to change this, since it's only used for testing. -
OidcSessionManagementSettings sessionManagementSettings
: contains settings for the session management spec:bool enabled
: (default false) whether to enable checking the session.Duration interval
: (default 5 minutes) how often to check the current user session.bool stopIfErrorReceived
: (default true) whether to stop checking the current user session if the OP sends anerror
message.
-
OidcPlatformSpecificOptions? options
: platform specific options to control auth requests:bool allowInsecureConnections
: Whether to allow non-HTTPS endpoints; forandroid
🤖 platform only.bool preferEphemeralSession
: Whether to use an ephemeral session that prevents cookies and other browser data being shared with the user's normal browser session. This property is only applicable toiOS
/MacOs
🍏 versions 13 and above.- Native options: these are options that apply to desktop 🖥️ platforms (linux + windows) that use loopback interface redirection, to customize how the server responds to redirect responses:
String? successfulPageResponse
: What to return if a URI is matchedString? methodMismatchResponse
: What to return if a method other thanGET
is requested.String? notFoundResponse
: What to return if a different path is used.
- Web 🌐 options:
OidcPlatformSpecificOptions_Web_NavigationMode navigationMode
: how the auth request is launched; possible values are:samePage
: NOT RECOMMENDED; since you have to reload your app and lose current ui state.newPage
: RECOMMENDED, navigates in a new tab.popup
: NOT RECOMMENDED; since some browsers block popups.you can also pass
popupWidth
andpopupHeight
to control the popup window dimensions.
String broadcastChannel
: The broadcast channel to use when receiving messages from the browser; defaults to:oidc_flutter_web/redirect
Note: This MUST be the same as the one in your redirect.html page.
Default Auth request parameters
These are parameters that you send with the auth request, and you can either define them in the login*
functions, or define the default values here.
List<String> scope
: possible values are inOidcConstants_Scopes
- openid
- profile
- address
- phone
List<String> prompt
: possible values are inOidcConstants_AuthorizeRequest_Prompt
- none
- login
- consent
- selectAccount
String? display
: possible values are inOidcConstants_AuthorizeRequest_Display
- page
- popup
- touch
- wap
List<String>? uiLocales
List<String>? acrValues
Duration? maxAge
Map<String, dynamic>? extraAuthenticationParameters
: these are extra parameters that you can send with every authentication request.Map<String, String>? extraTokenHeaders
: these are extra headers that you can send with every token request.Map<String, dynamic>? extraTokenParameters
: these are extra parameters that you can send with every token request.
httpClient
We depend on , and you can provide your own http client which will let you intercept and modify request/response behavior.
keyStore
A custom keystore from , if you want to maintain your own store. this is used for validating the JWT signature.
Initialization
After constructing the manager with the proper settings, you MUST call the manager.init()
function, which will do the following:
- If the function has already been initialized (checked via the hasInit variable), it simply returns. This is because certain configurations and setups do not need to be repeated.
- initialize the passed store (calls
OidcStore.init()
) - ensure that the discovery document has been retrieved (if the lazy constructor was used).
- if the discovery document contains a
jwks_uri
adds it the to the keystore. - handle various state management tasks. It loads logout requests and state results (from samePage redirects), also attempts to load cached tokens if there are no state results or logout requests.
- If the loaded token has expired and a refresh token exists, it attempts to refresh it, otherwise the token will be removed form the store.
- Starts Listening to incoming Front Channel Logout requests.
- Starts listening to token expiry events for automatic
refresh_token
circulation.
Login
loginAuthorizationCodeFlow
If you provided the settings earlier, there are no required parameters here, so you can just call manager.loginAuthorizationCodeFlow()
.
however there are some new parameters:
originalUri
: mainly used forsamePage
navigation on web; this is the Uri the user will navigate to after they are redirected to theredirect.html
pageextraStateData
: arbitrary data that you want to persist in the state parameter, note that this MUST be json serializable.idTokenHintOverride
: pass anid_token_hint
.includeIdTokenHintFromCurrentUser
: if true, will include the currentid_token
as theidTokenHint
parameter.Note that this is ignored if
idTokenHintOverride
is assigned.- if you pass
options
here and at the settings, they WILL NOT get merged, and the options from the login request takes precedence. -
extraParameters
: extra parameters that will get passed to the auth server as part of the request. These ARE merged withsettings.extraAuthenticationParameters
. -
extraTokenParameters
: extra parameters that will get passed to the auth server when making the token request. These ARE merged withsettings.extraTokenParameters
. extraTokenHeaders
: extra parameters that will get passed to the auth server when making the token request. These ARE merged withsettings.extraTokenHeaders
.
loginImplicitFlow
Same parameters as loginAuthorizationCodeFlow
, but you MUST specify the responseType
.
possible values are in OidcConstants_AuthorizationEndpoint_ResponseType
:
idToken
token
code
and any combination of them.
Warning
This flow is deprecated due to security concerns, and is only available for backward-compatibility with providers that don't support invoking the token endpoint without a client_secret (like google).
loginPassword
you only need to provide the username
and the password
.
you can also pass scopeOverride
to override the scopes from settings.scopes
.
Logout
The word "Logout" can have different behaviors depending on the context:
Forgetting the user
- this is as simple as calling
forgetUser()
which will clear the cache, and unassign thecurrentUser
. - this DOES NOT inform the identity provider that the user has logged out, nor revoke the token.
Note
A user that logs in after being forgotten, might not get prompted to enter their username/password again, keeping them in an infinite loop unable to change their credentials.
To counter this, you need to specify the prompt
parameter when logging in (e.g. prompt: ["login"]
), or logout from the Identity provider.
Logging out from the identity provider.
This can be done by calling logout
with the following optional parameters:
logoutHint
: can be the email/username or any documented value.postLogoutRedirectUriOverride
: if assigned, overrides the value passed insettings.postLogoutRedirectUri
.originalUri
: mainly used forsamePage
navigation on web; this is the Uri the user will navigate to after they are redirected to theredirect.html
page.extraStateData
: arbitrary data that you want to persist in the state parameter, note that this MUST be json serializable.uiLocalesOverride
: overrides the value passed insettings.uiLocales
.options
: platform-specific navigation options, which are the same assettings.options
.extraParameters
: extra parameters to pass to the logout request.
Listening to currentUser changes
Whenever a user logs in, logs out, or a token gets refreshed automatically, an event is added to the userChanges()
stream.
This is similar to firebase auth, and can be used to track the current session.
You can also get access to the current authenticated user via currentUser
property.
Listening to events
Events are an advanced form of user changes, since they occur in more places than the currentUser
stream, and help the developer hook into every flow.
for example you might want to make an API call before logging out the user, then you would do:
manager.events().listen((event) {
switch (event) {
case OidcPreLogoutEvent(:final currentUser):
// make an api call with the currentUser.
break;
default:
}
});
Refreshing the token manually
You can refresh the token manually by calling manager.refreshToken()
.
You can also override the refresh token manager.refreshToken(overrideRefreshToken: 'my_refresh_token')
.
It will either return OidcUser
with the new token, null
or throw an [OidcException].
null
is returned in the following cases:
- The discovery document doesn't have
grant_types_supported
includerefresh_token
- The current user is null.
- The current user's refresh token is null.
Overriding the discovery document
There are cases where you want to change some properties in the retrieved discovery document, either permanently, or for a specific method.
e.g., you might want to have a register and a login button where the register button overrides the discovery document's authorizationEndpoint
parameter, but the login button uses the idp provided value.
We use package:copy_with_extension_gen to generate copyWith
extension methods to help consumers override specific parts of the OidcProviderMetadata
discovery document.
Permanent override
You can do that via the discoveryDocument
setter in OidcUserManager
, e.g.
final userManager = OidcUserManager.lazy(/*...*/);
await userManager.init();
userManager.discoveryDocument = userManager.discoveryDocument.copyWith(/*...*/);
Per-method override
You can pass the OidcProviderMetadata? discoveryDocumentOverride
parameter in some methods.
Example:
final userManager = OidcUserManager.lazy(/*...*/);
await userManager.init();
await userManager.loginAuthorizationCodeFlow(
discoveryDocumentOverride: userManager.discoveryDocument.copyWith(
authorizationEndpoint: "https://idp.com/register", //change to register url instead of login
)
);
Dispose
If you aren't maintaining a single instance of the OidcUserManager
class, you might want to dispose()
it when you are done with the instance.
This will stop refreshing the tokens, and stop listening to logout requests.
It will also raise a done
event in the userChanges()
stream.
OidcFlutter
The class OidcFlutter
exposes static methods for the underlying platform implementations, if you don't want to use the OidcUserManager
class.
They are defined as such:
/// starts the authorization flow, and returns the response.
///
/// on android/ios/macos, if the `request.responseType` is set to anything other than `code`, it returns null.
///
/// NOTE: this DOES NOT do token exchange.
///
/// consider using [OidcEndpoints.getProviderMetadata] to get the [metadata] parameter if you don't have it.
static Future<OidcAuthorizeResponse?> getPlatformAuthorizationResponse({
required OidcProviderMetadata metadata,
required OidcAuthorizeRequest request,
OidcPlatformSpecificOptions options = const OidcPlatformSpecificOptions(),
});
/// starts the end session flow, and returns the response.
///
/// consider using [OidcEndpoints.getProviderMetadata] to get the [metadata] parameter if you don't have it.
static Future<OidcEndSessionResponse?> getPlatformEndSessionResponse({
required OidcProviderMetadata metadata,
required OidcEndSessionRequest request,
OidcPlatformSpecificOptions options = const OidcPlatformSpecificOptions(),
});
/// Listens to incoming front channel logout requests.
///
/// [listenTo] parameter determines which path should be listened for to receive
/// the request.
///
/// on windows/linux/macosx this starts a server on the same prt
static Stream<OidcFrontChannelLogoutIncomingRequest> listenToFrontChannelLogoutRequests({
required Uri listenTo,
OidcFrontChannelRequestListeningOptions options = const OidcFrontChannelRequestListeningOptions(),
});