From 2eb825416f04dd9f70d9d7e7b51d3809d7b209a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Mon, 25 Jan 2021 18:35:05 +0100 Subject: [PATCH] Add authorization storage documentation --- configuration/authorization-storage.md | 118 +++++++++++++++++++++++++ configuration/index.md | 19 +--- configuration/toc.yml | 5 +- 3 files changed, 123 insertions(+), 19 deletions(-) create mode 100644 configuration/authorization-storage.md diff --git a/configuration/authorization-storage.md b/configuration/authorization-storage.md new file mode 100644 index 0000000..2a067d9 --- /dev/null +++ b/configuration/authorization-storage.md @@ -0,0 +1,118 @@ +# Authorization storage + +To keep track of logical chains of tokens and user consents, OpenIddict supports storing authorizations +(also known as "grants" in some OpenID Connect implementations) in the database. + +## Types of authorizations + +Authorizations can be of two types: permanent and ad-hoc. + +### Permanent authorizations + +**Permanent authorizations are developer-defined authorizations** created using the `IOpenIddictAuthorizationManager.CreateAsync()` API +and explicitly attached to a `ClaimsPrincipal` using the OpenIddict-specific `principal.SetAuthorizationId()` extension method. + +Such authorizations are typically used to remember user consents and avoid displaying a consent view for each authorization request. +For that, a "consent type" can be defined per-application, as in the following example: + +```csharp +// Retrieve the application details from the database. +var application = await _applicationManager.FindByClientIdAsync(request.ClientId) ?? + throw new InvalidOperationException("The application cannot be found."); + +// Retrieve the permanent authorizations associated with the user and the application. +var authorizations = await _authorizationManager.FindAsync( + subject: await _userManager.GetUserIdAsync(user), + client : await _applicationManager.GetIdAsync(application), + status : Statuses.Valid, + type : AuthorizationTypes.Permanent, + scopes : request.GetScopes()).ToListAsync(); + +switch (await _applicationManager.GetConsentTypeAsync(application)) +{ + // If the consent is external (e.g when authorizations are granted by a sysadmin), + // immediately return an error if no authorization can be found in the database. + case ConsentTypes.External when !authorizations.Any(): + return Forbid( + authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = + Errors.ConsentRequired, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = + "The logged in user is not allowed to access this client application." + })); + + // If the consent is implicit or if an authorization was found, + // return an authorization response without displaying the consent form. + case ConsentTypes.Implicit: + case ConsentTypes.External when authorizations.Any(): + case ConsentTypes.Explicit when authorizations.Any() && + !request.HasPrompt(Prompts.Consent): + var principal = await _signInManager.CreateUserPrincipalAsync(user); + + // Note: in this sample, the granted scopes match the requested scope + // but you may want to allow the user to uncheck specific scopes. + // For that, simply restrict the list of scopes before calling SetScopes. + principal.SetScopes(request.GetScopes()); + principal.SetResources(await _scopeManager.ListResourcesAsync( + principal.GetScopes()).ToListAsync()); + + // Automatically create a permanent authorization to avoid requiring explicit consent + // for future authorization or token requests containing the same scopes. + var authorization = authorizations.LastOrDefault(); + if (authorization is null) + { + authorization = await _authorizationManager.CreateAsync( + principal: principal, + subject : await _userManager.GetUserIdAsync(user), + client : await _applicationManager.GetIdAsync(application), + type : AuthorizationTypes.Permanent, + scopes : principal.GetScopes()); + } + + principal.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization)); + + foreach (var claim in principal.Claims) + { + claim.SetDestinations(GetDestinations(claim, principal)); + } + + return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + + // At this point, no authorization was found in the database and an error must be returned + // if the client application specified prompt=none in the authorization request. + case ConsentTypes.Explicit when request.HasPrompt(Prompts.None): + case ConsentTypes.Systematic when request.HasPrompt(Prompts.None): + return Forbid( + authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = + Errors.ConsentRequired, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = + "Interactive user consent is required." + })); + + // In every other case, render the consent form. + default: return View(new AuthorizeViewModel + { + ApplicationName = await _applicationManager.GetLocalizedDisplayNameAsync(application), + Scope = request.Scope + }); +} +``` + +### Ad-hoc authorizations + +**Ad-hoc authorizations are automatically created by OpenIddict when a chain of tokens needs to be tracked for security reasons**, +but no explicit permanent authorization was attached by the developer to the `ClaimsPrincipal` used for the sign-in operation. + +Such authorizations are typically created in the authorization code flow to link all the tokens associated with the original authorization code, +so that they can be automatically revoked if the authorization code was redeemed multiple times (which may indicate a token leakage). +In the same vein, ad-hoc authorizations are also created when a refresh token is returned during a resource owner password credentials grant request. + +> [!INFO] +> When using the [OpenIddict.Quartz](https://www.nuget.org/packages/OpenIddict.Quartz/) integration, ad-hoc authorizations are automatically +> removed from the database after a short period of time (14 days by default). Unlike ad-hoc authorizations, permanent authorizations +> never removed from the database. \ No newline at end of file diff --git a/configuration/index.md b/configuration/index.md index 8f913ab..ea08c32 100644 --- a/configuration/index.md +++ b/configuration/index.md @@ -1,20 +1,3 @@ # Configuration and settings -
-
-
-
-

Application permissions

-

Learn how to leverage permissions to control the OIDC features a client is allowed to use.

-
-
-
-
-
-
-

Token formats

-

Learn more about the token formats supported by OpenIddict.

-
-
-
-
\ No newline at end of file +OpenIddict 3.0 comes with sensible defaults, but depending on the scenarios, the default settings can be amended to change how OpenIddict reacts to requests. \ No newline at end of file diff --git a/configuration/toc.yml b/configuration/toc.yml index 07724e5..a2e9af9 100644 --- a/configuration/toc.yml +++ b/configuration/toc.yml @@ -5,4 +5,7 @@ href: application-permissions.md - name: Token formats - href: token-formats.md \ No newline at end of file + href: token-formats.md + +- name: Authorization storage + href: authorization-storage.md \ No newline at end of file