diff --git a/configuration/proof-key-for-code-exchange.md b/configuration/proof-key-for-code-exchange.md new file mode 100644 index 0000000..6fabc0d --- /dev/null +++ b/configuration/proof-key-for-code-exchange.md @@ -0,0 +1,55 @@ +# Proof Key for Code Exchange + +Initially designed as a way to protect mobile applications from seeing their callback URIs hijacked by a malicious application installed on the same device, +the [Proof Key for Code Exchange (PKCE)](https://tools.ietf.org/html/rfc7636) mechanism has been extended to confidential clients to help mitigate authorization code leakages. +This mechanism is fully supported by all versions of OpenIddict and can be enforced globally or per-client to block authorization requests that don't send PKCE parameters. + +## Enabling PKCE enforcement at the global level + +Proof Key for Code Exchange can be enforced globally by calling `options.RequireProofKeyForCodeExchange()` in the server options: + +```csharp +services.AddOpenIddict() + .AddServer(options => + { + options.RequireProofKeyForCodeExchange(); + }); +``` + +## Enabling PKCE enforcement per client + +Proof Key for Code Exchange can also be enforced per-client by adding it to the list of requirements attached to a client: + +```csharp +await manager.CreateAsync(new OpenIddictApplicationDescriptor +{ + ClientId = "mvc", + ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654", + ConsentType = ConsentTypes.Explicit, + PostLogoutRedirectUris = + { + new Uri("https://localhost:44381/signout-callback-oidc") + }, + RedirectUris = + { + new Uri("https://localhost:44381/signin-oidc") + }, + Permissions = + { + Permissions.Endpoints.Authorization, + Permissions.Endpoints.Logout, + Permissions.Endpoints.Token, + Permissions.GrantTypes.AuthorizationCode, + Permissions.GrantTypes.RefreshToken, + Permissions.ResponseTypes.Code, + Permissions.Scopes.Email, + Permissions.Scopes.Profile, + Permissions.Scopes.Roles, + Permissions.Prefixes.Scope + "demo_api" + }, + Requirements = + { + Requirements.Features.ProofKeyForCodeExchange + } +}); +``` \ No newline at end of file diff --git a/configuration/toc.yml b/configuration/toc.yml index 356803f..c3e7c47 100644 --- a/configuration/toc.yml +++ b/configuration/toc.yml @@ -10,6 +10,9 @@ - name: Claim destinations href: claim-destinations.md +- name: Proof Key for Code Exchange + href: proof-key-for-code-exchange.md + - name: Token formats href: token-formats.md diff --git a/guide/choosing-the-right-flow.md b/guide/choosing-the-right-flow.md new file mode 100644 index 0000000..dbe9479 --- /dev/null +++ b/guide/choosing-the-right-flow.md @@ -0,0 +1,210 @@ +# Choosing the right flow + +OpenIddict offers built-in support for all the standard flows defined by the +[OAuth 2.0](https://tools.ietf.org/html/rfc6749) and [OpenID Connect](https://openid.net/specs/openid-connect-core-1_0.html) core specifications: +[the authorization code flow](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth), +[the implicit flow](https://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth), +[the hybrid flow](https://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth) (which is basically a mix between the first two flows), +[the resource owner password credentials grant](https://tools.ietf.org/html/rfc6749#section-4.3) and +[the client credentials grant](https://tools.ietf.org/html/rfc6749#section-4.4). + +While not specific to OpenIddict, choosing the best flow(s) for your application is an **important prerequisite** +when implementing your own authorization server ; so here's a quick overview of the different OAuth 2.0/OpenID Connect flows: + +------------------------ +## Non-interactive flows + +### Resource owner password credentials flow (not recommended for new applications) + +Directly inspired by [basic authentication](https://en.wikipedia.org/wiki/Basic_access_authentication), the resource owner password credentials grant +(abbreviated *ROPC*) is conceptually **the simplest OAuth 2.0 flow**: the client application asks the user his username/password, sends a token request +to the authorization server with the user credentials (and depending on the client authentication policy defined by the authorization server, +its own client credentials) and gets back an access token it can use to retrieve the user's resources. + +![Resource owner password credentials flow](choosing-the-right-flow/resource-owner-password-flow.png) + +```http +POST /connect/token HTTP/1.1 +Host: server.example.com +Content-Type: application/x-www-form-urlencoded + +grant_type=password&username=johndoe&password=A3ddj3w +``` + +```http +HTTP/1.1 200 OK +Content-Type: application/json;charset=UTF-8 +Cache-Control: no-store +Pragma: no-cache + +{ + "access_token":"2YotnFZFEjr1zCsicMWpAA", + "token_type":"bearer", + "expires_in":3600 +} +``` + +> [!CAUTION] +> This flow is **not recommended by the OAuth 2.0 specification** as it's the only grant type where **the user password is directly exposed to the client application**, +> which breaks the principle of least privilege and **makes it unsuitable for third-party client applications that can't be fully trusted by the authorization server**. + +> While popular and trivial to implement (as it doesn't involve any redirection or consent form and unlike interactive flows, doesn't require implementing +> cross-site request forgery (XSRF) countermeasures to prevent session fixation attacks), **its use in new applications is not recommended**. Instead, +> users are encouraged to use the authorization code flow, that doesn't expose passwords to client applications and is not limited to password authentication. + + + +------------------------------------------------------------------------------- +### Client credentials grant (recommended for machine-to-machine communication) + +The client credentials grant is almost identical to the resource owner password credentials grant, except it's been specifically designed for **client-to-server scenarios** +(no user is involved in this flow): the client application sends a token request containing its credentials and gets back an access token it can use to query its own resources. + +![Client credentials flow](choosing-the-right-flow/client-credentials-flow.png) + +```http +POST /connect/token HTTP/1.1 +Host: server.example.com +Content-Type: application/x-www-form-urlencoded + +grant_type=client_credentials&client_id=s6BhdRkqt3&client_secret=gX1fBat3bV +``` + +```http +HTTP/1.1 200 OK +Content-Type: application/json;charset=UTF-8 +Cache-Control: no-store +Pragma: no-cache + +{ + "access_token":"2YotnFZFEjr1zCsicMWpAA", + "token_type":"bearer", + "expires_in":3600 +} +``` + +> [!NOTE] +> Unlike the resource owner password credentials grant, **client authentication is not optional** when using the client credentials grant and +> **OpenIddict will always reject unauthenticated token requests**, [as required by the OAuth 2.0 specification](https://tools.ietf.org/html/rfc6749#section-4.4.2). +> +> This means that **you CAN'T use the client credentials grant with public applications** like browser, +> mobile or desktop applications, as they are not able to keep their credentials secret. + +-------------------- +## Interactive flows + +### Authorization code flow (recommended for new applications) + +While the authorization code flow is probably the most complicated flow (as it involves both **user-agent redirections and backchannel communication**), it's +**the recommended flow for any scenario involving end users, whether they log in using a password, a PIN, a smart card or even an external provider**. +In return for its complexity, this flow has a great advantage when used in server-side applications: the `access_token` cannot be intercepted by the user agent. + +There are basically 2 steps in the authorization code flow: the authorization request/response and the token request/response. + +![Authorization code flow](choosing-the-right-flow/authorization-code-flow.png) + +- **Step 1: the authorization request** + +In this flow, the client application always initiates the authentication process by generating an authorization request including +the mandatory `response_type=code` parameter, its `client_id`, its `redirect_uri` and optionally, a `scope` and a `state` parameter +[that allows flowing custom data and helps mitigate XSRF attacks](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest). + +> [!NOTE] +> In most cases, the client application will simply return a 302 response with a `Location` header to redirect the user agent to the authorization endpoint, +> but depending on the OpenID Connect client you're using, POST requests might also be supported to allow you to send large authorization requests. +> This feature [is usually implemented using an auto-post HTML form](https://github.com/aspnet/Security/pull/392). + +```http +HTTP/1.1 302 Found +Location: https://server.example.com/authorize?response_type=code&client_id=s6BhdRkqt3&state=af0ifjsldkj&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb +``` + +```http +GET /connect/authorize?response_type=code&client_id=s6BhdRkqt3&state=af0ifjsldkj&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb HTTP/1.1 +Host: server.example.com +``` + +The way the identity provider handles the authorization request is implementation-specific but in most cases, a consent form +is displayed to ask the user if he or she agrees to share his/her personal data with the client application. + +![Consent form](choosing-the-right-flow/consent-form.png) + +When the consent is given, the user agent is redirected back to the client application with **a unique and short-lived token** +named *authorization code* that the client will be able to exchange with an access token by sending a token request. + +```http +HTTP/1.1 302 Found +Location: https://client.example.org/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=af0ifjsldkj +``` + +> [!WARNING] +> To prevent XSRF/session fixation attacks, **the client application MUST ensure that the `state` parameter returned by the identity provider +> corresponds to the original `state`** and stop processing the authorization response if the two values don't match. +> [This is usually done by generating a non-guessable string and a corresponding correlation cookie](https://tools.ietf.org/html/rfc6749#section-10.12). + +- **Step 2: the token request** + +When the client application gets back an authorization code, it must immediately reedem it for an access token by sending a `grant_type=authorization_code` token request. + +> [!NOTE] +> To help the identity provider [mitigate counterfeit clients attacks](https://tools.ietf.org/html/rfc6819#section-4.4.1.7), the original `redirect_uri` must also be sent. +> +> If the client application is a confidential application (i.e an application that has been assigned client credentials), authentication is required. + +```http +POST /connect/token HTTP/1.1 +Host: server.example.com +Content-Type: application/x-www-form-urlencoded + +grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb&client_id=s6BhdRkqt3&client_secret=gX1fBat3bV&scope=openid +``` + +```http +HTTP/1.1 200 OK +Content-Type: application/json;charset=UTF-8 +Cache-Control: no-store +Pragma: no-cache + +{ + "access_token":"2YotnFZFEjr1zCsicMWpAA", + "token_type":"bearer", + "expires_in":3600 +} +``` + +> [!NOTE] +> To increase security, additional parameters such as `code_challenge` and `code_challenge_method` can be specified to bind the authorization code +> that will be returned by the authorization endpoint to the original authorization request. This mechanism is known as +> [Proof Key for Code Exchange](../configuration/proof-key-for-code-exchange.md) and is fully supported by OpenIddict. + +-------------------------------------------------------- +### Implicit flow (not recommended for new applications) + +The implicit flow is similar to the authorization code flow, **except there's no token request/response step**: the access token is directly returned +to the client application as part of the authorization response in the URI fragment (or in the request form when using `response_mode=form_post`). + +![Implicit flow](choosing-the-right-flow/implicit-flow.png) + +```http +GET /connect/authorize?response_type=token&client_id=s6BhdRkqt3&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb&scope=openid&state=af0ifjsldkj&nonce=n-0S6_WzA2Mj HTTP/1.1 +Host: server.example.com +``` + +```http +HTTP/1.1 302 Found +Location: https://client.example.org/cb#access_token=SlAV32hkKG&token_type=bearer&expires_in=3600&state=af0ifjsldkj +``` + +> [!CAUTION] +> Initially designed for browser applications, this flow is inherently less secure than the authorization code flow and doesn't support +> [Proof Key for Code Exchange](https://tools.ietf.org/html/rfc7636). As such, using it in new applications is not recommended. + +> [!WARNING] +> To prevent XSRF/session fixation attacks, **the client application MUST ensure that the `state` parameter returned by the identity provider +> corresponds to the original `state`** and stop processing the authorization response if the two values don't match. +> [This is usually done by generating a non-guessable string and a corresponding value stored in the local storage](https://tools.ietf.org/html/rfc6749#section-10.12). +> +> When using the implicit flow, **the client application MUST also ensure that the access token was not issued +> to another application to prevent [confused deputy attacks](https://stackoverflow.com/a/17439317/542757).** +> With OpenID Connect, this can be done by using `response_type=id_token token` and checking the `aud` claim +> of the JWT identity token, that must correspond or contain the `client_id` of the client application. \ No newline at end of file diff --git a/guide/choosing-the-right-flow/authorization-code-flow.png b/guide/choosing-the-right-flow/authorization-code-flow.png new file mode 100644 index 0000000..4d723fa Binary files /dev/null and b/guide/choosing-the-right-flow/authorization-code-flow.png differ diff --git a/guide/choosing-the-right-flow/client-credentials-flow.png b/guide/choosing-the-right-flow/client-credentials-flow.png new file mode 100644 index 0000000..4c31090 Binary files /dev/null and b/guide/choosing-the-right-flow/client-credentials-flow.png differ diff --git a/guide/choosing-the-right-flow/consent-form.png b/guide/choosing-the-right-flow/consent-form.png new file mode 100644 index 0000000..7f30dec Binary files /dev/null and b/guide/choosing-the-right-flow/consent-form.png differ diff --git a/guide/choosing-the-right-flow/implicit-flow.png b/guide/choosing-the-right-flow/implicit-flow.png new file mode 100644 index 0000000..622c0f4 Binary files /dev/null and b/guide/choosing-the-right-flow/implicit-flow.png differ diff --git a/guide/choosing-the-right-flow/resource-owner-password-flow.png b/guide/choosing-the-right-flow/resource-owner-password-flow.png new file mode 100644 index 0000000..fdbf3eb Binary files /dev/null and b/guide/choosing-the-right-flow/resource-owner-password-flow.png differ diff --git a/guide/toc.yml b/guide/toc.yml index 9ad1168..41307dc 100644 --- a/guide/toc.yml +++ b/guide/toc.yml @@ -4,6 +4,9 @@ - name: Getting started href: getting-started.md +- name: Choosing the right flow + href: choosing-the-right-flow.md + - name: Migration guides items: - name: Migration from 2.0 to 3.0