AspNetCore.Docs/aspnetcore/security/authentication/identity-api-authorization.md

25 KiB

title author description monikerRange ms.author ms.date uid
Use Identity to secure a Web API backend for SPAs tdykstra Learn how to use Identity to secure a Web API backend for single page applications (SPAs). >= aspnetcore-3.0 tdykstra 05/01/2024 security/authentication/identity/spa

How to use Identity to secure a Web API backend for SPAs

[!INCLUDE]

:::moniker range=">= aspnetcore-8.0"

ASP.NET Core Identity provides APIs that handle authentication, authorization, and identity management. The APIs make it possible to secure endpoints of a Web API backend with cookie-based authentication. A token-based option is available for clients that can't use cookies, but in using this you are responsible for ensuring the tokens are kept secure. We recommend using cookies for browser-based applications, because, by default, the browser automatically handles them without exposing them to JavaScript.

This article shows how to use Identity to secure a Web API backend for SPAs such as Angular, React, and Vue apps. The same backend APIs can be used to secure Blazor WebAssembly apps.

Prerequisites

The steps shown in this article add authentication and authorization to an ASP.NET Core Web API app that:

  • Isn't already configured for authentication.
  • Targets net8.0 or later.
  • Can be either minimal API or controller-based API.

Some of the testing instructions in this article use the Swagger UI that's included with the project template. The Swagger UI isn't required to use Identity with a Web API backend.

Install NuGet packages

Install the following NuGet packages:

For the quickest way to get started, use the in-memory database.

Change the database later to SQLite or SQL Server to save user data between sessions when testing or for production use. That introduces some complexity compared to in-memory, as it requires the database to be created through migrations, as shown in the EF Core getting started tutorial.

Install these packages by using the NuGet package manager in Visual Studio or the dotnet add package CLI command.

Create an IdentityDbContext

Add a class named ApplicationDbContext that inherits from xref:Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityDbContext%601:

:::code language="csharp" source="~\security\authentication\identity-api-authorization\8samples\APIforSPA\ApplicationDbContext.cs":::

The code shown provides a special constructor that makes it possible to configure the database for different environments.

Add one or more of the following using directives as needed when adding the code shown in these steps.

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

Configure the EF Core context

As noted earlier, the simplest way to get started is to use the in-memory database. With in-memory each run starts with a fresh database, and there's no need to use migrations. After the call to WebApplication.CreateBuilder(args), add the following code to configure Identity to use an in-memory database:

:::code language="csharp" source="~\security\authentication\identity-api-authorization\8samples\APIforSPA\Program.cs" id="snippetAppDbContext":::

To save user data between sessions when testing or for production use, change the database later to SQLite or SQL Server.

Add Identity services to the container

After the call to WebApplication.CreateBuilder(args), call xref:Microsoft.Extensions.DependencyInjection.AuthorizationServiceCollectionExtensions.AddAuthorization%2A to add services to the dependency injection (DI) container:

:::code language="csharp" source="~\security\authentication\identity-api-authorization\8samples\APIforSPA\Program.cs" id="snippetAddAuthorization":::

Activate Identity APIs

After the call to WebApplication.CreateBuilder(args), call xref:Microsoft.Extensions.DependencyInjection.IdentityServiceCollectionExtensions.AddIdentityApiEndpoints%60%601(Microsoft.Extensions.DependencyInjection.IServiceCollection) and xref:Microsoft.Extensions.DependencyInjection.IdentityEntityFrameworkBuilderExtensions.AddEntityFrameworkStores%60%601(Microsoft.AspNetCore.Identity.IdentityBuilder).

:::code language="csharp" source="~\security\authentication\identity-api-authorization\8samples\APIforSPA\Program.cs" id="snippetActivateAPIs":::

By default, both cookies and proprietary tokens are activated. Cookies and tokens are issued at login if the useCookies query string parameter in the login endpoint is true.

Map Identity routes

After the call to builder.Build(), call xref:Microsoft.AspNetCore.Routing.IdentityApiEndpointRouteBuilderExtensions.MapIdentityApi%60%601(Microsoft.AspNetCore.Routing.IEndpointRouteBuilder) to map the Identity endpoints:

:::code language="csharp" source="~\security\authentication\identity-api-authorization\8samples\APIforSPA\Program.cs" id="snippetMapEndpoints":::

Secure selected endpoints

To secure an endpoint, use the xref:Microsoft.AspNetCore.Builder.AuthorizationEndpointConventionBuilderExtensions.RequireAuthorization%2A extension method on the Map{Method} call that defines the route. For example:

:::code language="csharp" source="~\security\authentication\identity-api-authorization\8samples\APIforSPA\Program.cs" id="snippetRequireAuthorization" highlight="15":::

The RequireAuthorization method can also be used to:

  • Secure Swagger UI endpoints, as shown in the following example:

    :::code language="csharp" source="~\security\authentication\identity-api-authorization\8samples\APIforSPA\Program.cs" id="snippetSwaggerAuth":::

  • Secure with a specific claim or permission, as shown in the following example:

    :::code language="csharp" source="~\security\authentication\identity-api-authorization\8samples\APIforSPA\Program.cs" id="snippetRequireAdmin":::

In a controller-based web API project, secure endpoints by applying the [Authorize] attribute to a controller or action.

Test the API

A quick way to test authentication is to use the in-memory database and the Swagger UI that's included with the project template. The following steps show how to test the API with the Swagger UI. Make sure that the Swagger UI endpoints aren't secured.

Attempt to access a secured endpoint

  • Run the app and navigate to the Swagger UI.
  • Expand a secured endpoint, such as /weatherforecast in a project created by the web API template.
  • Select Try it out.
  • Select Execute. The response is 401 - not authorized.

Test registration

  • Expand /register and select Try it out.

  • In the Parameters section of the UI, a sample request body is shown:

    {
      "email": "string",
      "password": "string"
    }
    
  • Replace "string" with a valid email address and password, and then select Execute.

    To comply with the default password validation rules, the password must be at least six characters long and contain at least one of each of the following characters:

    • Uppercase letter
    • Lowercase letter
    • Numeric digit
    • Nonalphanumeric character

    If you enter an invalid email address or a bad password, the result includes the validation errors. Here's an example of a response body with validation errors:

    {
      "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
      "title": "One or more validation errors occurred.",
      "status": 400,
      "errors": {
        "PasswordTooShort": [
          "Passwords must be at least 6 characters."
        ],
        "PasswordRequiresNonAlphanumeric": [
          "Passwords must have at least one non alphanumeric character."
        ],
        "PasswordRequiresDigit": [
          "Passwords must have at least one digit ('0'-'9')."
        ],
        "PasswordRequiresLower": [
          "Passwords must have at least one lowercase ('a'-'z')."
        ]
      }
    }
    

    The errors are returned in the ProblemDetails format so the client can parse them and display validation errors as needed.

    A successful registration results in a 200 - OK response.

Test login

  • Expand /login and select Try it out. The example request body shows two additional parameters:

    {
      "email": "string",
      "password": "string",
      "twoFactorCode": "string",
      "twoFactorRecoveryCode": "string"
    }
    

    The extra JSON properties aren't needed for this example and can be deleted. Set useCookies to true.

  • Replace "string" with the email address and password that you used to register, and then select Execute.

    A successful login results in a 200 - OK response with a cookie in the response header.

Retest the secured endpoint

After a successful login, rerun the secured endpoint. The authentication cookie is automatically sent with the request, and the endpoint is authorized. Cookie-based authentication is securely built-in to the browser and "just works."

Testing with nonbrowser clients

Some web clients might not include cookies in the header by default:

  • If you're using a tool for testing APIs, you might need to enable cookies in the settings.

  • The JavaScript fetch API doesn't include cookies by default. Enable them by setting credentials to the value include in the options.

  • An HttpClient running in a Blazor WebAssembly app needs the HttpRequestMessage to include credentials, like the following example:

    request.SetBrowserRequestCredential(BrowserRequestCredentials.Include);
    

Use token-based authentication

We recommend using cookies in browser-based applications, because, by default, the browser automatically handles them without exposing them to JavaScript.

A custom token (one that is proprietary to the ASP.NET Core identity platform) is issued that can be used to authenticate subsequent requests. The token is passed in the Authorization header as a bearer token. A refresh token is also provided. This token allows the application to request a new token when the old one expires without forcing the user to log in again.

The tokens aren't standard JSON Web Tokens (JWTs). The use of custom tokens is intentional, as the built-in Identity API is meant primarily for simple scenarios. The token option isn't intended to be a full-featured identity service provider or token server, but instead an alternative to the cookie option for clients that can't use cookies.

To use token-based authentication, set the useCookies query string parameter to false when calling the /login endpoint. Tokens use the bearer authentication scheme. Using the token returned from the call to /login, subsequent calls to protected endpoints should add the header Authorization: Bearer <token> where <token> is the access token. For more information, see Use the POST /login endpoint later in this article.

Log out

To provide a way for the user to log out, define a /logout endpoint like the following example:

:::code language="csharp" source="~\security\authentication\identity-api-authorization\8samples\APIforSPA\Program.cs" id="snippetLogout":::

Provide an empty JSON object ({}) in the request body when calling this endpoint. The following code is an example of a call to the logout endpoint:

public signOut() {
  return this.http.post('/logout', {}, {
    withCredentials: true,
    observe: 'response',
    responseType: 'text'

The MapIdentityApi<TUser> endpoints

The call to MapIdentityApi<TUser> adds the following endpoints to the app:

Use the POST /register endpoint

The request body must have xref:Microsoft.AspNetCore.Identity.Data.LoginRequest.Email and xref:Microsoft.AspNetCore.Identity.Data.LoginRequest.Password properties:

{
  "email": "string",
  "password": "string",
}

For more information, see:

Use the POST /login endpoint

In the request body, xref:Microsoft.AspNetCore.Identity.Data.LoginRequest.Email and xref:Microsoft.AspNetCore.Identity.Data.LoginRequest.Password are required. If two-factor authentication (2FA) is enabled, either xref:Microsoft.AspNetCore.Identity.Data.LoginRequest.TwoFactorCode or xref:Microsoft.AspNetCore.Identity.Data.LoginRequest.TwoFactorRecoveryCode is required. If 2FA isn't enabled, omit both twoFactorCode and twoFactorRecoveryCode. For more information, see Use the POST /manage/2fa endpoint later in this article.

Here's a request body example with 2FA not enabled:

{
  "email": "string",
  "password": "string"
}

Here are request body examples with 2FA enabled:

  • {
      "email": "string",
      "password": "string",
      "twoFactorCode": "string",
    }
    
  • {
      "email": "string",
      "password": "string",
      "twoFactorRecoveryCode": "string"
    }
    

The endpoint expects a query string parameter:

  • useCookies - Set to true for cookie-based authentication. Set to false or omit for token-based authentication.

For more information about cookie-based authentication, see Test login earlier in this article.

Token-based authentication

If useCookies is false or omitted, token-based authentication is enabled. The response body includes the following properties:

{
  "tokenType": "string",
  "accessToken": "string",
  "expiresIn": 0,
  "refreshToken": "string"
}

For more information about these properties, see xref:Microsoft.AspNetCore.Authentication.BearerToken.AccessTokenResponse.

Put the access token in a header to make authenticated requests, as shown in the following example

Authorization: Bearer {access token}

When the access token is about to expire, call the /refresh endpoint.

Use the POST /refresh endpoint

For use only with token-based authentication. Gets a new access token without forcing the user to log in again. Call this endpoint when the access token is about to expire.

The request body contains only the xref:Microsoft.AspNetCore.Identity.Data.RefreshRequest.RefreshToken. Here's a request body example:

{
  "refreshToken": "string"
}

If the call is successful, the response body is a new xref:Microsoft.AspNetCore.Authentication.BearerToken.AccessTokenResponse, as shown in the following example:

{
  "tokenType": "string",
  "accessToken": "string",
  "expiresIn": 0,
  "refreshToken": "string"
}

Use the GET /confirmEmail endpoint

If Identity is set up for email confirmation, a successful call to the /register endpoint sends an email that contains a link to the /confirmEmail endpoint. The link contains the following query string parameters:

  • userId
  • code
  • changedEmail - Included only if the user changed the email address during registration.

Identity provides default text for the confirmation email. By default, the email subject is "Confirm your email" and the email body looks like the following example:

 Please confirm your account by <a href='https://contoso.com/confirmEmail?userId={user ID}&code={generated code}&changedEmail={new email address}'>clicking here</a>.

If the xref:Microsoft.AspNetCore.Identity.SignInOptions.RequireConfirmedEmail property is set to true, the user can't log in until the email address is confirmed by clicking the link in the email. The /confirmEmail endpoint:

  • Confirms the email address and enables the user to log in.
  • Returns the text "Thank you for confirming your email." in the response body.

To set up Identity for email confirmation, add code in Program.cs to set RequireConfirmedEmail to true and add a class that implements xref:Microsoft.AspNetCore.Identity.UI.Services.IEmailSender to the DI container. For example:

:::code language="csharp" source="~/security/authentication/identity-api-authorization/8samples/APIforSPA/Program.cs" id="snippetConfigureEmail":::

For more information, see xref:security/authentication/accconfirm.

Identity provides default text for the other emails that need to be sent as well, such as for 2FA and password reset. To customize these emails, provide a custom implementation of the IEmailSender interface. In the preceding example, EmailSender is a class that implements IEmailSender. For more information, including an example of a class that implements IEmailSender, see xref:security/authentication/accconfirm.

Use the POST /resendConfirmationEmail endpoint

Sends an email only if the address is valid for a registered user.

The request body contains only the xref:Microsoft.AspNetCore.Identity.Data.ResendConfirmationEmailRequest.Email. Here's a request body example:

{
  "email": "string"
}

For more information, see Use the GET /confirmEmail endpoint earlier in this article.

Use the POST /forgotPassword endpoint

Generates an email that contains a password reset code. Send that code to /resetPassword with a new password.

The request body contains only the xref:Microsoft.AspNetCore.Identity.Data.ForgotPasswordRequest.Email. Here's an example:

{
  "email": "string"
}

For information about how to enable Identity to send emails, see Use the GET /confirmEmail endpoint.

Use the POST /resetPassword endpoint

Call this endpoint after getting a reset code by calling the /forgotPassword endpoint.

The request body requires xref:Microsoft.AspNetCore.Identity.Data.ResetPasswordRequest.Email, xref:Microsoft.AspNetCore.Identity.Data.ResetPasswordRequest.ResetCode, and xref:Microsoft.AspNetCore.Identity.Data.ResetPasswordRequest.NewPassword. Here's an example:

{
  "email": "string",
  "resetCode": "string",
  "newPassword": "string"
}

Use the POST /manage/2fa endpoint

Configures two-factor authentication (2FA) for the user. When 2FA is enabled, successful login requires a code produced by an authenticator app in addition to the email address and password.

Enable 2FA

To enable 2FA for the currently authenticated user:

  • Call the /manage/2fa endpoint, sending an empty JSON object ({}) in the request body.

  • The response body provides the xref:Microsoft.AspNetCore.Identity.Data.TwoFactorResponse.SharedKey along with some other properties that aren't needed at this point. The shared key is used to set up the authenticator app. Response body example:

    {
      "sharedKey": "string",
      "recoveryCodesLeft": 0,
      "recoveryCodes": null,
      "isTwoFactorEnabled": false,
      "isMachineRemembered": false
    }
    
  • Use the shared key to get a Time-based one-time password (TOTP). For more information, see xref:security/authentication/identity-enable-qrcodes.

  • Call the /manage/2fa endpoint, sending the TOTP and "enable": true in the request body. For example:

    {
      "enable": true,
      "twoFactorCode": "string"
    }
    
  • The response body confirms that xref:Microsoft.AspNetCore.Identity.Data.TwoFactorResponse.IsTwoFactorEnabled is true and provides the xref:Microsoft.AspNetCore.Identity.Data.TwoFactorResponse.RecoveryCodes. The recovery codes are used to log in when the authenticator app isn't available. Response body example after successfully enabling 2FA:

    {
      "sharedKey": "string",
      "recoveryCodesLeft": 10,
      "recoveryCodes": [
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string"
      ],
      "isTwoFactorEnabled": true,
      "isMachineRemembered": false
    }
    

Log in with 2FA

Call the /login endpoint, sending the email address, password, and TOTP in the request body. For example:

{
  "email": "string",
  "password": "string",
  "twoFactorCode": "string"
}

If the user doesn't have access to the authenticator app, log in by calling the /login endpoint with one of the recovery codes that was provided when 2FA was enabled. The request body looks like the following example:

{
  "email": "string",
  "password": "string",
  "twoFactorRecoveryCode": "string"
}

Reset the recovery codes

To get a new collection of recovery codes, call this endpoint with xref:Microsoft.AspNetCore.Identity.Data.TwoFactorRequest.ResetRecoveryCodes set to true. Here's a request body example:

{
  "resetRecoveryCodes": true
}

Reset the shared key

To get a new random shared key, call this endpoint with xref:Microsoft.AspNetCore.Identity.Data.TwoFactorRequest.ResetSharedKey set to true. Here's a request body example:

{
  "resetSharedKey": true
}

Resetting the key automatically disables the two-factor login requirement for the authenticated user until it's re-enabled by a later request.

Forget the machine

To clear the cookie "remember me flag" if present, call this endpoint with xref:Microsoft.AspNetCore.Identity.Data.TwoFactorRequest.ForgetMachine set to true. Here's a request body example:

{
  "forgetMachine": true
}

This endpoint has no impact on token-based authentication.

Use the GET /manage/info endpoint

Gets email address and email confirmation status of the logged-in user. Claims were omitted from this endpoint for security reasons. If claims are needed, use the server-side APIs to set up an endpoint for claims. Or instead of sharing all of the users' claims, provide a validation endpoint that accepts a claim and responds whether the user has it.

The request doesn't require any parameters. The response body includes the xref:Microsoft.AspNetCore.Identity.Data.InfoResponse.Email and xref:Microsoft.AspNetCore.Identity.Data.InfoResponse.IsEmailConfirmed properties, as in the following example:

{
  "email": "string",
  "isEmailConfirmed": true
}

Use the POST /manage/info endpoint

Updates the email address and password of the logged-in user. Send xref:Microsoft.AspNetCore.Identity.Data.InfoRequest.NewEmail, xref:Microsoft.AspNetCore.Identity.Data.InfoRequest.NewPassword, and xref:Microsoft.AspNetCore.Identity.Data.InfoRequest.OldPassword in the request body, as shown in the following example:

{
  "newEmail": "string",
  "newPassword": "string",
  "oldPassword": "string"
}

Here's an example of the response body:

{
  "email": "string",
  "isEmailConfirmed": false
}

See also

For more information, see the following resources:

:::moniker-end

[!INCLUDE]