ASP.NET Core in Action 25 Authentication and authorization for APIs

25 Authentication and authorization for APIs

This chapter covers

• Seeing how authentication works for APIs in ASP.NET Core
• Using bearer tokens for authentication
• Testing APIs locally with JSON Web Tokens
• Applying authorization policies to minimal APIs

In chapter 23 you learned how authentication works with traditional web apps, such as those you would build with Razor Pages or Model-View-Controller (MVC) controllers. Traditional web apps typically use encrypted cookies to store the identity of a user for a request, which the AuthenticationMiddleware then decodes. In this chapter you’ll learn how authentication works for API applications, how it differs from traditional web apps, and what options are available.

We start by taking a high-level look at how authentication works for APIs, both in isolation and when they’re part of a larger application or distributed system. You’ll learn about some of the protocols involved, such as OAuth 2.0 and OpenID Connect; patterns you can use to protect your APIs; and the tokens used to control access, typically JSON Web Tokens, called JWTs.

In section 25.3 you’ll learn how to put this knowledge into practice, adding authentication to a minimal API application using JWTs. In section 25.4 you’ll learn how to use the .NET command-line interface (CLI) to generate JWTs for testing your API locally.

The .NET CLI works well for generating tokens, but you need a way to add this token to a request. Specifically, if you’re using OpenAPI definitions and Swagger UI as described in chapter 11, you need a way to tell Swagger about your authentication requirements. In section 25.5 you’ll learn about some of the authentication configuration options for your OpenAPI documents and how to use Swagger UI to send authenticated requests to your API.

Finally, in section 25.6 I show how to apply authorization policies to minimal API endpoints to restrict which users can call your APIs. The authorization concepts you learned about in chapter 24 for Razor Pages are the same for APIs, so you’re still using claims, requirements, handlers, and polices.

We’ll start off by looking at how authentication works when you have an API application. Many of the authentication concepts are similar to traditional apps, but the requirement to support multiple types of users, traditional apps, client-side apps, and mobile apps has led to subtly different solutions.

25.1 Authentication for APIs and distributed applications

In this section you’ll learn about the authentication process for API applications, why it typically differs from authentication for traditional web apps, and some of the common patterns and protocols that are involved.

25.1.1 Extending authentication to multiple apps

I outlined the authentication process for traditional web apps in chapter 23. When a user signs in to your application, you set an encrypted cookie. This cookie contains a serialized version of the ClaimsPrincipal of the user, including their ID and any associated claims. When you make a second request, the browser automatically sends this cookie. The AuthenticationMiddleware then decodes the cookie, deserializes the ClaimsPrincipal, and sets the current user for the request, as shown previously in figure 23.3 and reproduced in figure 25.1.

alt text

Figure 25.1 When a user first signs in to an app, the app sets an encrypted cookie containing the ClaimsPrincipal. On subsequent requests, the cookie sent with the request contains the user principal, which is deserialized, validated, and used to authenticate the request.

This flow works particularly well when you have a single traditional web app that’s doing all the work. The app is responsible for authenticating and managing users, as well as serving your app data and executing business logic, as shown in figure 25.2.

alt text

Figure 25.2 Traditional apps typically handle all the functionality of an app: the business logic, generating the UI, authentication, and user management.

In addition to traditional web apps, it’s common to use ASP.NET Core as an API to serve data for mobile and client-side single-page applications (SPAs). Similarly, even traditional web apps using Razor Pages often need to call API applications behind the scenes, as shown in figure 25.3.

alt text

Figure 25.3 Modern applications typically need to expose web APIs for mobile and client-side apps, as well as potentially calling APIs on the backend. When all these services need to authenticate and manage users, this becomes logistically complicated.

In this situation you have multiple apps and APIs, all of which need to understand that the same user is logically making a request across all the apps and APIs. If you keep the same approach as before, where each app manages its own users, things can quickly become unmanageable!

You’d need to duplicate all the sign-in logic between the apps and APIs, as well as have some central database holding the user details. Users would likely need to sign in multiple times to access different parts of the service. On top of that, using cookies becomes problematic for some mobile clients in particular or where you’re making requests to multiple domains (as cookies belong to only a single domain). So how can we improve this? By moving the authentication responsibilities to a separate service.

25.1.2 Centralizing authentication in an identity provider

Modern systems often have many moving parts, each of which requires some level of authentication and authorization to protect each app from unauthorized use. Instead of embedding authentication responsibilities in each application, a common approach is to extract the code that’s common to all the apps and APIs and then move it to an identity provider, as shown in figure 25.4.

alt text

Figure 25.4 An alternative architecture involves using a central identity provider to handle all the authentication and user management for the system. Tokens are passed back and forth among the identity provider, apps, and APIs.

Instead of signing in to an app directly, the app redirects to an identity provider. The user signs in to this identity provider, which passes bearer tokens back to the client (a browser or mobile app, for example) to indicate who the user is and what they’re allowed to access. The client can pass these tokens to the APIs to provide information about the logged-in user without needing to reauthenticate or manage users directly in the API.

DEFINITION Bearer tokens are strings that contain authentication details about a user or app. They may or may not be encrypted but are typically signed to avoid tampering. JWTs are the most common format. We’ll look more at JWTs in section 25.2.

Using a separate identity provider is clearly more complicated on the face of it, as you’ve thrown a whole new service into the mix, but in the long run this has several advantages:

• Users can share their identity among multiple services. As you’re logged in to the central identity provider, you’re essentially logged in to all apps that use that service. This gives you the single-sign-on experience, where you don’t have to keep logging in to multiple services.
• You don’t need to duplicate sign-in logic between multiple services. All the sign-in logic is encapsulated in the identity provider, so you don’t need to add sign-in screens to all your apps.
• The identity provider has a single responsibility. The identity provider is responsible only for authentication and managing users. In many cases, this is generic enough that you can (and should!) use a third-party identity service, such as Auth0 or Azure Active Directory, instead of building your own.
• You can easily add new sign-in mechanisms. Whether you use the identity provider approach or the traditional approach, it’s possible to use external services to handle the authentication of users. You’ll have seen this in apps that allow you to “log in using Facebook” or “log in using Google,” for example. If you use a centralized identity provider, you can add support for more providers in one place instead of having to configure every app and API explicitly.

Out of the box, ASP.NET Core supports architectures like this and for consuming bearer tokens from identity providers, but it doesn’t include support for issuing those tokens in the core framework. That means you’ll need to use another library or service as the identity provider.

As I mentioned in chapter 23, one excellent option is to use a third-party identity provider, such as Facebook, Google, Okta, Auth0, or Azure Active Directory. These providers take care of storing user passwords, authenticating using modern standards like WebAuthn (https://webauthn.guide), and looking for malicious attempts to impersonate users.

By using an identity provider, you leave the tricky security details to the experts and can focus on the core purpose of your business, whichever domain that is. Not all providers are equal, though: For some providers (such as Auth0) you own the profiles, whereas for others (Facebook or Google) you don’t. Make sure to choose a provider that matches your requirements.

Tip Wherever possible, I recommend using a third-party identity provider. Well-respected identity providers have many experts working solely on securing your customers’ details, proactively preventing attacks and ensuring that the data is safe. By leaving this tricky job to the experts, you’re free to focus on the core business of your app, whatever that may be.

Another common option is to build your own identity provider. This may sound like a lot of work (and it is!), but thanks to excellent libraries like OpenIddict (https://github.com/openiddict) and Duende’s IdentityServer (https://duendesoftware.com), it’s perfectly possible to write your own identity provider to serve bearer tokens that can be consumed by your apps and APIs.

WARNING You should consider carefully whether the effort and risks associated with creating your own identity provider are worthwhile. Bugs are a fact of life, and a bug in your identity provider could easily result in a security vulnerability. Nevertheless, if you have specific identity requirements, creating your own identity provider may be a reasonable or necessary option.

An aspect often overlooked by people getting started with OpenIddict and IdentityServer is that they aren’t prefabricated solutions. They consist of a set of services and middleware that you add to a standard ASP.NET Core app, providing an implementation of relevant identity standards, according to the specification. You, as a developer, still need to write the profile management code that knows how to create a new user (normally in a database), load a user’s details, validate their password, and manage their associated claims. On top of that, you need to provide all the UI code for the user to log in, manage their passwords, and configure two-factor authentication (2FA). It’s not for the faint of heart!

In many ways, you can think of an identity provider as a traditional web app that has only account management pages. If you want to take on building your own identity provider, ASP.NET Core Identity, described in chapter 23, provides a good basis for the user management side. Adding IdentityServer or OpenIddict gives you the ability to generate tokens for other services, using the OpenID Connect standard, for maximum interoperability with other services.

25.1.3 OpenID Connect and OAuth 2.0

OpenID Connect (OIDC) (http://openid.net/connect) is an authentication protocol built on top of the OAuth 2.0 (https://oauth.net/2) specification. It’s designed to facilitate the kind of approaches described in section 25.1.2, where you want to leave the responsibility of storing user credentials to someone else (an identity provider). It provides an answer to the question “Which user sent this request?” without your having to manage the user yourself.

NOTE It isn’t strictly necessary to understand these protocols to add authentication to your APIs, but I think it’s best to have a basic understanding of them so that you understand where your APIs fit into the security landscape. If you want to learn more about OpenID Connect, OpenID Connect in Action, by Prabath Siriwardena (Manning, 2023), provides lots more details.

Open ID Connect is built on top of the OAuth 2.0 protocol, so it helps to understand that protocol a little first. OAuth 2.0 is an authorization protocol. It allows a user to delegate access of a resource to a different service in a controlled manner without revealing any additional details, such as your identity or any other information.

That’s all a bit abstract, so let’s consider an example. You want to print some photos of your dog through a photo printing service, dogphotos.com. You sign up to the dogphotos.com service, and they give you two options for uploading your photos:

• Upload from your computer.
• Download directly from Facebook using OAuth 2.0.

As you’re using a new laptop, you haven’t downloaded all the photos of your dog to your computer, so you choose to use OAuth 2.0 instead, as shown in figure 25.5. This triggers the following sequence:

  1. dogphotos.com redirects you to Facebook, where you must sign in (if you haven’t already).
  2. Once you’re authenticated, Facebook shows a consent screen, which describes the data dogphotos.com wants to access, which should be your photos only in this case.
  3. When you choose OK, Facebook automatically redirects you to a URL on dogphotos.com and includes an authorization code in the URL.
  4. dogphotos.com uses this code, in combination with a secret known only by Facebook and dogphotos.com, to retrieve an access token from Facebook.
  5. Finally, dogphotos.com uses the token to call the Facebook API and retrieve your dog photos!

alt text

Figure 25.5 Using OAuth 2.0 to authorize dogphotos.com to access your photos on Facebook

There’s a lot going on in this example, but it gives some nice benefits:

• You didn’t have to give your Facebook credentials to dogphotos.com. You simply signed in to Facebook as normal.
• You had control of which details dogphotos.com could access on your behalf via the Facebook photos API.
• You didn’t have to give dogphotos.com any of your identity information (though in practice, this is often requested).

Effectively, you delegated your access of the Facebook photos API to dogphotos.com. This approach is why OAuth 2.0 is described as an authorization protocol, not an authentication protocol. dogphotos.com doesn’t know your identity on Facebook; it is authorized only to access the photos API on behalf of someone.

OAuth 2.0 authorization flows and grant types
The OAuth 2.0 example shows in this section uses a common flow or grant type, as it’s called in OAuth 2.0, for obtaining a token from an identity provider. Oauth 2.0 defines several grant types and extensions, each designed for a different scenario:

• Authorization code—This is the flow I described in figure 25.5, in which an application uses the combination of an authorization code and a secret to retrieve a token.

• Proof Key for Code Exchange (PKCE)—This is an extension to the authorization code that you should always favor, if possible, as it provides additional protections against certain attacks, as described in the RFC at https://www.rfc-editor.org/rfc/rfc7636.

• Client credentials—This is used when no user is involved, such as when you have an API talking to another API.

Many more grants are available (see https://oauth.net/2/grant-types), and each grant is suited to a different situation. The examples are the most common types, but if your scenario doesn’t match these, it’s worth exploring the other OAuth 2.0 grants available before thinking you need to invent your own! And with Oauth 2.1 coming soon (http://mng.bz/XNav), there may well be updated guidance to be aware of.

OAuth 2.0 is great for the scenario I’ve described so far, in which you want to delegate access to a resource (your photos) to someone else (dogphotos.com). But it’s also common for apps to want to know your identity in addition to accessing an API. For example, dogphotos.com may want to be able to contact you via Facebook if there’s a problem with your photos.

This is where OpenID Connect comes in. OpenID Connect takes the same basic flows as OAuth 2.0 and adds some conventions, discoverability, and authentication. At a high level, OpenID Connect treats your identity (such as an ID or email address) as a resource that is protected in the same way as any other API. You still need to consent to give dogphotos.com access to your identity details, but once you do, it’s an extra API call for dogphotos.com to retrieve your identity details, as shown in figure 25.6.

alt text

Figure 25.6 Using OpenID Connect to authenticate with Facebook and retrieve identity information. The overall flow is the same as with Oauth 2.0, as shown in figure 25.5, but with an additional identity token describing the authentication event and API call to retrieve the identity details.

OpenID Connect is a crucial authentication component in many systems, but if you’re building the API only (for example, the Facebook photos API from figures 25.5 and 25.6), all you really care about are the tokens in the requests; how that token was obtained is less important from a technical standpoint. In the next section we’ll look in detail at these tokens and how they work.

25.2 Understanding bearer token authentication

In this section you’ll learn about bearer tokens: what they are, how they can be used for security with APIs, and the common JWT format for tokens. You’ll learn about some of the limitations of the tokens, approaches to work around these, and some common concepts such as audiences and scopes.

The name bearer token consists of two parts that describe its use:

• Token—A security token is a string that provides access to a protected resource.
• Bearer—A bearer token is one in which anyone who has the token (the bearer) can use it like anyone else. You don’t need to prove that you were the one who received the token originally or have access to any additional key. You can think of a bearer token as being a bit like money: if it’s in your possession, you can spend it!

If the second point makes you a little uneasy, that’s good. You should think of bearer tokens as being a lot like passwords: you must protect them at all costs! You should avoid including bearer tokens in URL query strings, for example, as these may be automatically logged, exposing the token accidentally.

Everything old is new again: Cookies for APIs
Bearer token authentication is extremely common for APIs, but as with everything in tech, the landscape is constantly evolving. One area that has seen a lot of change is the process of securing SPAs like React, Angular, and Blazor WASM. The advice for some years was to use the Authorization code with PKCE grant (https://www.rfc-editor.org/rfc/rfc8252#section-6), but the big problem with this pattern is that the bearer tokens for calling the API are ultimately stored in the browser.

An alternative pattern has emerged recently: the Backend for Frontend (BFF) pattern. In this approach, you have a traditional ASP.NET Core application (the backend, which hosts the Blazor WASM or other SPA application (the frontend). The main job of the ASP.NET Core application is to handle OpenID Connect authentication, store the bearer tokens securely, and set an authentication cookie, exactly like a traditional web app.

The frontend app in the browser sends requests to the backend app, which automatically includes the cookie. The backend swaps out the authentication cookie for the appropriate bearer token and forwards the request to the real API.

The big advantages of this approach are that no bearer tokens are ever sent to the browser, and much of the frontend code is significantly simplified. The main down side is that you need to run the additional backend service to support the frontend app. Nevertheless, this is quickly becoming the recommended approach. You can read more about the pattern in Duende’s documentation at http://mng.bz/yQdB. Alternatively, you can find a project template for the BFF pattern from Damien Boden at http://mng.bz/MBlW.

Bearer tokens don’t have to have any particular value; they could be a completely random string, for example. However, the most common format and the format used by OpenID Connect is a JWT. JWTs (defined in https://www.rfc-editor.org/rfc/rfc7519.html) consist of three parts:

• A JavaScript Object Notation (JSON) header describing the token
• A JSON payload containing the claims
• A binary signature created from the header and the payload

Each part is base64-encoded and concatenated with a '.' into a single string that can be safely passed in HTTP headers, for example, as shown in figure 25.7. The signature is created using key material that must be shared by the provider that created the token and any API that consumes it. This ensures that the JWT can’t be tampered with, such as to add extra claims to a token.

WARNING Always validate the signature of any JWTs you consume, as described in the JWT Best Current Practices RFC (https://www.rfc-editor.org/rfc/rfc8725). ASP.NET Core does this by default.

alt text

Figure 25.7 An example JWT, decoded using the website https://jwt.io. The JWT consists of three parts: the header, the payload, and the signature. You must always verify the signature of any JWTs you receive.

Figure 25.7 shows the claims included in the JWT, some of which have cryptic names like iss and iat. These are standard claim names used in OpenID Connect (standing for “Issuer” and “Issued at,” respectively). You generally don’t need to worry about these, as they’re automatically handled by ASP.NET Core when it decodes the token. Nevertheless, it’s helpful to understand what some of these claims mean, as it will help when things go wrong:

• sub—The subject of the token, the unique identifier of the subject it’s describing. This will often be a user, in which case it may be the identity provider’s unique ID for the user.
• aud—The audience of the token, specifying the domains for which this token was created. When an API validates the token, the API should confirm that the JWT’s aud claim contains the domain of the API.
• scope—The scopes granted in the token. Scopes define what the user/app consented to (and is allowed to do). Taking the example from section 25.1, dogphotos.com may have requested the photos.read and photos.edit scopes, but if the user consented only to the photos.read scope, the photos.edit scope would not be in the JWT it receives for use with the Facebook photos API. It’s up to the API itself to interpret what each scope means for the business logic of the request.
• exp—The expiration time of the token, after which it is no longer valid, expressed as the number of seconds since midnight on January 1, 1970 (known as the Unix timestamp).

An important point to realize is that JWTs are not encrypted. That means anyone can read the contents of a JWT by default. Another standard, JSON Web Encryption (JWE), can be used to wrap a JWT in an encrypted envelope that can’t be read unless you have the key. Many identity providers include support for using JWEs with nested JWTs, and ASP.NET Core includes support for both out of the box, so it’s something to consider.

Bearer tokens, access tokens, reference tokens, oh my!
The concept of a bearer token described in this section is a generic idea that can be used in several ways and for different purposes. You’ve already read about access tokens and identity tokens used in OpenID Connect. These are both bearer tokens; their different names describe the purpose of the token.

The following list describes some of the types of tokens you might read about or run into:

• Access token—Access tokens are used to authorize access to a resource. These are the tokens typically referred to when you talk about bearer authentication. They come in two flavors:

Self-contained—These are the most common tokens, with JWT as the most common format. They contain metadata, claims, and a signature. The strength of self-contained tokens—that they contain all the data and can be validated offline—is also their weakness, as they can’t be revoked. Due to this, they typically have a limited valid lifespan. They can also become large if they contain many claims, which increases request sizes.

Reference token—These don’t contain any data and are typically a random string. When a protected API receives a reference token, it must exchange the reference token with the identity provider for the claims (for example, a JWT). This approach ensures more privacy, as the claims are never exposed to the client, and the token can be revoked at the identity provider. However, it requires an extra HTTP round trip every time the API receives a request. This makes reference tokens a good option for high-security environments, where the performance effect is less critical.

• ID token—This token is used in OpenID Connect (http://mng.bz/a1M7) to describe an authentication event. It may contain additional claims about the authenticated user, but this is not required; if the claims aren’t provided in the ID token, they can be retrieved from the identity provider’s UserInfo endpoint. The ID token is always a JWT, but you should never send it to other APIs; it is not an access token. The ID token can also be used to log out the user at the identity provider.

• Refresh token—For security reasons, access tokens typically have relatively short lifetimes, sometimes as low as 5 minutes. After this time, the access token is no longer valid, and you need to retrieve a new one. Making users log in to their identity provider every 5 minutes is clearly a bad experience, so as part of the OAuth or OpenID Connect flow you can also request a refresh token.

When an access token expires, you can send the refresh token to an identity provider, and it returns a new access token without the user’s needing to log in again. The power to obtain valid access tokens means that it’s critical to protect refresh tokens; should an attacker obtain a refresh token, they effectively have the power to impersonate a user.

In most of your work building and interacting with APIs, you’ll likely be using self-contained JWT access tokens. These are what I’m primarily referring to in this chapter whenever I mention bearer tokens or bearer authentication.

Now you know what a token is, as well as how they’re issued by identity providers using the OpenID Connect and OAuth 2.0 protocols. Before we get to some code in section 25.3, we’ll see what a typical authentication flow looks like for an ASP.NET Core API app using JWT bearer tokens for authentication.

At a high level, authenticating using bearer tokens is identical to authenticating using cookies for a traditional app that has already authenticated, which you saw in figure 25.1. The request to the API contains the bearer token in a header. Any middleware before the authentication middleware sees the request as unauthenticated, exactly the same as for cookie authentication, as shown in figure 25.8.

alt text

Figure 25.8 When an API request contains a bearer token, the token is validated and deserialized by the authentication middleware. The middleware creates a ClaimsPrincipal from the token, optionally transforming it with additional claims, and sets the HttpContext.User property. Subsequent middleware sees the request as authenticated.

Things are a bit different in the AuthenticationMiddleware. Instead of deserializing a cookie containing the ClaimsPrincipal, the middleware decodes the JWT token in the Authorization header. It validates the signature using the signing keys from the identity provider, and verifies that the audience has the expected value and that the token has not expired.

If the token is valid, the authentication middleware creates a ClaimsPrincipal representing the authenticated request and sets it on HttpContext.User. All middleware after the authentication middleware sees the request as authenticated.

TIP If the claims in the token don’t match the key values you’re expecting, you can use claims transformation to remap claims. This applies to cookie authentication too, but it’s particularly common when you’re receiving tokens from third-party identity providers, where you don’t control the names of claims. You can also use this approach to add extra claims for a user, which weren’t in the original token. To learn more about claims transformation, see http://mng.bz/gBJV.

We’ve covered a lot of theory about JWT tokens in this chapter, so you’ll be pleased to hear it’s time to look at some code!

25.3 Adding JWT bearer authentication to minimal APIs

In this section you’ll learn how to add JWT bearer token authentication to an ASP.NET Core app. I use the minimal API Recipe API application we started in chapter 12 in this chapter, but the process is identical if you’re building an API application using web API controllers.

.NET 7 significantly simplified the number of steps you need to get started with JWT authentication by adding some conventions, which we’ll discuss shortly. To add JWT to an existing API application, first install the Microsoft.AspNetCore.Authentication.JwtBearer NuGet package using the .NET CLI

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

or by adding the to your project directly:

<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer"
    Version="7.0.0" />

Next, add the required services to configure JWT authentication for your application, as shown in listing 25.1. As you may remember, the authentication and authorization middleware are automatically added to your middleware pipeline by WebApplication, but if you want to control the position of the middleware, you can override the location, as I do here.

Listing 25.1 Adding JWT bearer authentication to a minimal API application

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication() ❶
.AddJwtBearer(); ❷
builder.Services.AddAuthorization(); ❸
builder.Services.AddScoped<RecipeService>();
WebApplication app = builder.Build();
app.UseAuthentication(); ❹
app.UseAuthorization(); ❺
app.MapGet("/recipe", async (RecipeService service) =>
{
return await service.GetRecipes();
}).RequireAuthorization(); ❻
app.Run();

❶ Adds the core authentication services
❷ Adds and configures JWT authentication
❸ Adds the core authorization services
❹ Adds the authentication middleware
❺ Adds the authorization middleware
❻ Adds an authorization policy to the minimal API endpoint

As well as configuring the JWT authentication, listing 25.1 adds an authorization policy to the one minimal API endpoint shown in the app. The RequireAuthorization() function adds a simple “Is authenticated” authorization policy to the endpoint. This is exactly analgous to when you add an [Authorize] attribute to MVC or Web API controllers. Any requests for this endpoint must be authenticated; otherwise, the request is rejected by the authorization middleware with a 401 Unauthorized reponse, as shown in figure 25.9.

alt text

Figure 25.9 If you send a request to an API protected with JWT bearer authentication and don’t include a token, you’ll receive a 401 Unauthorized challenge response.

Authentication schemes: Choosing between cookies and bearer tokens
One question you may have while reading about bearer authentication is how the authentication middleware knows whether to look for the cookie or a header. The answer is authentication schemes.

An authentication scheme in ASP.NET Core has an ID and an associated authentication handler that controls how the user is authenticated, as well as how authentication and authorization failures should be handled.

For example, in chapter 23 the cookie authentication scheme was used implicitly by ASP.NET Core Identity. The cookie authentication handler in this case authenticates users by looking for a cookie and redirects users to the login or “access denied” pages for authentication or authorization failures.

In listing 25.1 you registered the JWT Bearer authentication scheme. The JWT bearer authentication handler reads tokens from the Authorization header and returns 401 and 403 responses for authentication or authorization failures.

When you register only a single authentication scheme, such as in listing 25.1, ASP.NET Core automatically sets that as the default, but it’s possible to register multiple authentication schemes. This is particularly common if you are using OpenID Connect with a traditional web app, for example. In these cases you can choose which scheme is used for authentication events or authentication failures and how the schemes should interact.

Using multiple authentication schemes can be confusing, so it’s important to follow the documentation closely when configuring authentication for your app. You can read more about authentication schemes at http://mng.bz/5w1a. If you need only a single scheme, you shouldn’t have any problems, but otherwise, here be dragons!

Great! The 401 response in figure 25.9 verifies that the app is behaving correctly for unauthenticated requests. The obvious next step is to send a request to your API that includes a valid JWT bearer token. Unfortunately, this is where things traditionally get tricky. How do you generate a valid JWT? Luckily, in .NET 7, the .NET CLI comes with a tool to make creating test tokens easy.

25.4 Using the user-jwts tool for local JWT testing

In section 25.3 you added JWT authentication to your application and protected your API with a basic authorization policy. The problem is that you can’t test your API unless you can generate JWT tokens. In production you’ll likely have an identity provider such as Auth0, Azure Active Directory, or IdentityServer to generate tokens for you using OpenID Connect. But that can make for cumbersome local testing. In this section you’ll learn how to use the .NET CLI to generate JWTs for local testing.

In .NET 7, the .NET CLI includes a tool called user-jwts that you can use to generate tokens. This tool acts as a mini identity provider, meaning that you can generate tokens with any claims you may need, and your API can verify them using signing key material generated by the tool.

TIP The user-jwts tool is built into the software development kit (SDK), so there’s nothing extra to install. You need to enable User Secrets for your project, but user-jwts will do this for you if you haven’t already. The user-jwts tool uses User Secrets to store the signing key material used to generate the JWTs, which your app uses to validate the JWT signatures.

Let’s look at how to create a JWT with the user-jwts tool and use that to send a request to our application.

25.4.1 Creating JWTs with the user-jwts tool

To create a JWT that you can use in requests to your API, run the following with the user-jwts tool from inside your project folder:

dotnet user-jwts create

This command does several things:

• Enables User Secrets in the project if they’re not already configured, as though you had manually run dotnet user-secrets init.
• Adds the signing key material to User Secrets, which you can view by running dotnet user-secrets list as described in chapter 10, which prints out the key material configuration, as in this example:

Authentication:Schemes:Bearer:SigningKeys:0:Value =
    rIhUzB3DIbtbUwiIxkgoKfFDkLpY+gIJOB4eaQzczq8=
Authentication:Schemes:Bearer:SigningKeys:0:Length = 32
Authentication:Schemes:Bearer:SigningKeys:0:Issuer = dotnet-user-jwts
Authentication:Schemes:Bearer:SigningKeys:0:Id = c99a872d

• Configures the JWT authentication services to support tokens generated by the user-jwts tool by adding configuration to appsettings.Development.json, as follows:

{
  "Authentication": {
    "Schemes": {
      "Bearer": {
        "ValidAudiences": [
          "http://localhost:5073",
          "https://localhost:7112"
        ],
        "ValidIssuer": "dotnet-user-jwts"
      }
    }
  }
}

The user-jwts tool automatically configures the valid audiences based on the profiles in your launchSettings.json file. All the applicationUrls listed in launchSettings.json are listed as valid audiences, so it doesn’t matter which profile you use to run your app; the generated token should be valid. The JWT bearer authentication service automatically reads this configuration and configures itself to support user-jwts JWTs.

• Creates a JWT. By default, the token is created with a sub and unique_claim set to your operating system’s username, with aud claims for each of the applicationUrls in your launchSettings.json and an issuer of dotnet-user-jwts. You’ll notice that these match the values added to your APIs configuration file.
After calling dotnet user-jwts create, the JWT token is printed to the console, along with the sub name used and the ID of the token. I’ve truncated the tokens throughout this chapter for brevity:

New JWT saved with ID 'f2080e51'.
Name: andrewlock

Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImFuZHJl…

TIP You can visualize exactly what’s in the token by copy and pasting it into https://jwt.io, as I showed in figure 25.7.

Now that you have a token, it’s time to test it. To use the token, you need to add an Authorization header to requests using the following format (where is the full token printed by user-jwts):

Authorization: Bearer <token>

If any part of this header is incorrect—if you misspell Authorization, misspell Bearer, don’t include a space between Bearer and your token, or mistype your token—you’ll get a 401 Unauthorized response.

TIP If you get 401 Unauthorized responses even after adding an Authorization header to your requests, double-check your spelling, and make sure that the token is added correct with the "Bearer " prefix. Typos have a way of creeping in here! You can also increase the logging level in your API to see why failures are happening, as you’ll learn in chapter 26.

Once you have added the token you can call your API, which should now return successfully, as shown in figure 25.10.

alt text

Figure 25.10 Sending a request with an Authorization Bearer using Postman. The Authorization header must have the format Bearer . You can also configure this in the Authorization tab of Postman.

The default token created by the JWT is sufficient to authenticate with your API, but depending on your requirements, you may want to customize the JWT to add or change claims. In the next section you’ll learn how.

25.4.2 Customizing your JWTs

By default, the user-jwts tool creates a bare-bones JWT that you can use to call your app. If you need more customization, you can pass extra options to the dotnet user-jwts create command to control the JWT it generates. Some of the most useful options are

• --name sets the sub and unique_name claims for the JWT instead of using the operating system user as the name.
• --claim = adds a claim called with value to the JWT. Use this option multiple times to add claims.
• --scope adds a scope claim called to the JWT. Use this option multiple times to add scopes.

These aren’t the only options; you can control essentially everything about the generated JWT. Run dotnet user-jwts create --help to see all the options available. One option that may be useful in certain automated scripts or tests is the --output option. This controls how the JWT is printed to the console after creation. The default value, default, prints a summary of the JWT and the token itself, as you saw previously:

New JWT saved with ID 'f2080e51'.
Name: andrewlock

Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImFuZHJl…

This is handy if you’re creating tokens ad hoc at the command line, but the alternative output options may be more useful for scripts. For example, running

dotnet user-jwts create --output token

outputs the token only,

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImFuZHJl…

which is much more convenient if you’re trying to parse the output in a script, for example. Alternatively, you can pass --output json, which prints details about the JWT instead, as in this example:

{
  "Id": "8bf9b2fd",
  "Scheme": "Bearer",
  "Name": "andrewlock",
  "Audience": " https://localhost:7236, http://localhost:5229",
  "NotBefore": "2022-10-22T17:50:26+00:00",
  "Expires": "2023-01-22T17:50:26+00:00",
  "Issued": "2022-10-22T17:50:26+00:00",
  "Token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6Im…",
  "Scopes": [],
  "Roles": [],
  "CustomClaims": {}
}

Note that this isn’t the payload of the token; it’s the configuration details used to create the JWT. The token itself is exposed in the Token field. Again, this may be useful if you’re generating JWTs using a script and need to parse the output.

25.4.3 Managing your local JWTs

When you’re generating a JWT, the user-jwts tool automatically saves the JWT configuration (the JSON shown in section 25.4.2) to your hard drive. This is stored next to the secrets.json file that contains the User Secrets, in a location that varies depending on your operating system and the in your project file:

• Windows—%APPDATA%\Microsoft\UserSecrets\\user-jwts.json
• Linux and macOS—~/.microsoft/usersecrets//user-jwts.json

As for User Secrets, JWTs created by user-jwts aren’t encrypted, but they’re outside your project directory, so they are a better approach to managing secrets locally. The generated JWTs should be used only for local testing; you should be using a real identity provider for production systems to securely produce JWTs for a logged-in user. This is the reason why the user-jwts tool updates only appsettings.Development.json with the required configuration, not appsettings.json; it stops you from accidentally using user-jwts in production. You should add your production identity provider details in appsettings.json instead.

As well as editing the user-jwts.json file manually, you can use the user-jwts tool to manage the JWTs stored locally. In addition to using create, you can call dotnet user-jwts from the project folder, where is one of the following options:

• list—Lists a summary of all the tokens stored in user-jwts.json for the project.
• clear—Deletes all the tokens created for a project.
• remove—Deletes a single token for the project, using the token ID displayed by the list command.
• print—Outputs the details of a single JWT, using the token ID, as key value pairs.
• key—Can be used to view or reset the signing key material of tokens stored in the User Secrets Manager. Note that resetting the key material renders all previous JWTs generated by the tool invalid.

The user-jwts tool is handy for generating JWTs locally, but you must remember to add it to your local testing tool for all requests. If you’re using Postman for testing, you need to add the JWT to your request, as I showed in figure 25.10. However, if you’re using Swagger UI as I described in chapter 11, things aren’t quite that simple. In the next section you’ll learn how to describe your authorization requirements in your OpenAPI document.

25.5 Describing your authentication requirements to OpenAPI

In chapter 11 you learned how to add an OpenAPI document to your ASP.NET Core app that describes your API. This is used to power tooling such as automatic client generation, as well as Swagger UI. In this section you’ll learn how to add authentication requirements to your OpenAPI document so you can test your API using Swagger UI with tokens generated by the user-jwts tool.

One of the slightly annoying things about adding authentication and authorization to your APIs is that it makes testing harder. You can’t just fire a web request from a browser; you must use a tool like Postman that you can add headers to. Even for command-line aficionados, curl commands can become unwieldy once you need to add authorization headers. And tokens expire and are typically harder to generate. The list goes on!

I’ve seen these difficulties lead people to disable authentication requirements for local testing or to try to add them only late in a product’s life cycle. I strongly suggest you don’t do this! Trying to add real authentication late in a project is likely to cause headaches and bugs that you could easily have caught if you weren’t trying to work around the security complexity.

TIP Add real authentication and authorization to your APIs as soon as you understand the requirements, as you will likely catch more security-related bugs.

The user-jwts tool can help significantly with these challenges, as you can easily generate tokens in a format you need, optionally with a long expiration (so you don’t need to keep renewing them) without having to wrestle with an identity provider directly. Nevertheless, you need a way to add these tokens to whichever tool you use for testing, such as Swagger UI.

Swagger UI is based on the OpenAPI definition of your API, so the best (and easiest) way to add support for authentication to Swagger UI is to update the security requirements of your application in your OpenAPI document. This consists of two steps:

• Define the security scheme your API uses, such as OAuth 2.0, OpenID Connect, or simple Bearer authentication.
• Declare which endpoints in your API use the security scheme.

The following listing shows how to configure an OpenAPI document using Swashbuckle for an API that uses JWT bearer authentication. The values defined on OpenApiSecurityScheme match the default settings configured by the user-jwts tool when you use AddJwtBearer(). AddSecurityDefinition() defines a security scheme for your API, and AddSecurityRequirement() declares that the whole API is protected using the security scheme.

Listing 25.2 Adding bearer authentication to an OpenAPI document using Swashbuckle

WebApplicationBuilder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication().AddJwtBearer();
builder.Services.AddAuthorization();

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(x =>
{
    x.SwaggerDoc("v1", new OpenApiInfo {
        Title = "Recipe App", Version = "v1" });

    var security = new OpenApiSecurityScheme    #A
    {
        Name = HeaderNames.Authorization,    #B
        Type = SecuritySchemeType.ApiKey,    #C
        In = ParameterLocation.Header,    #D
        Description = "JWT Authorization header",  #E
        Reference = new OpenApiReference
        {
            Id = JwtBearerDefaults.AuthenticationScheme,  #F
            Type = ReferenceType.SecurityScheme   #G
        }
    };

    x.AddSecurityDefinition(security.Reference.Id, security);  #H
    x.AddSecurityRequirement(new OpenApiSecurityRequirement   #I
        {{security, Array.Empty<string>()}});  #I
});

var app = builder.Build();

app.UseSwagger();
app.UseSwaggerUI();

app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

app.MapGet("/", () => "Hello world!").RequireAuthorization();
app.Run();

❶ Defines the security used by your API
❷ The name of the header to use (required)
❸ The type of security; may be OAuth2 or OpenIdConnect if using those (required)
❹ Where the token will be provided (required)
❺ A friendly description of the scheme, used in the UI
❻ A unique ID for the scheme. This uses the default JWT scheme name.
❼ The type of OpenID object (required)
❽ Adds the security definition to the OpenAPI document
❾ Marks the whole API as protected by the security definition

When you run your application after adding the definition to your OpenAPI document, you should see an Authorize button in the top-right corner of Swagger UI, as shown in figure 25.11. Choosing this button opens a dialog box describing your authentication scheme, including a text box to enter your token. You must enter Bearer in this box with a space between them. Choose Authorize, which saves the value, and then Close. Now when you send a request to the API, Swagger UI attaches the token in the Authorization header, and the request succeeds.

alt text

Figure 25.11 Adding an Authorization header using Swagger UI. When adding the token, ensure that you enter Bearer , including the Bearer prefix. Swagger UI then attaches the token to all subsequent requests, so you are authorized to call the API.

If you’re specifically using OpenID Connect or OAuth 2.0 to protect your APIs, you can configure these in the OpenApiSecurityScheme document instead of using bearer authentication. In that case, choosing Authorize in Swagger UI would redirect you to your identity provider to sign in and retrieve a token without your having to copy and paste anything. That’s extremely useful if you’re running an identity provider locally or exposing Swagger UI in production.

The example in listing 25.2 shows the configuration when your whole API is protected by an authorization requirement. That’s the most common situation in my experience, but you may want to expose certain endpoints to anonymous users without any authorization requirements. In that case, you can configure Swashbuckle to conditionally apply the requirement to only those endpoints with a requirement.

TIP See the Swashbuckle documentation to learn how to configure this and many other features related to OpenAPI document generation: http://mng.bz/6D1A. Swashbuckle is highly extensible, but as always, it’s worth considering whether the added complexity you introduce to achieve perfect documentation of your API is worth the tradeoff. For publicly exposed OpenAPI documents, this may well be the case, but for local testing or internal APIs, the argument may be harder to make.

In this chapter we’ve looked in depth at using JWT bearer tokens for authentication and explored the parallels with cookie authentication for traditional apps. In the final section of this chapter we look at authorization and how you can apply different authorization policies to your minimal API endpoints.

25.6 Applying authorization policies to minimal API endpoints

So far in this chapter we’ve focused on authentication: the process of validating the identity of the request initiator. For APIs, this typically requires decoding and validating a JWT bearer token in the authentication middleware and setting the ClaimsPrincipal for the request, as you saw in section 25.2. In this section we look at the next stage in protecting your APIs, authorization, and how you can apply different authorization requirements to your minimal API endpoints.

The good news is that authorization for minimal APIs is essentially identical to the authorization process you learned about in chapter 24 for Razor Pages and MVC controllers. The same concept of authorization policies, requirements, handlers, and claims-based authorization apply in the same way and use the exact same services. Figure 25.12 shows how this looks for a request to a minimal API endpoint protected with bearer authentication, which is remarkably similar to the Razor Pages equivalent in figure 24.2.

alt text

Figure 25.12 Authorizing a request to a minimal API endpoint. The routing middleware selects an endpoint that is protected by an authorization requirement. The authentication middleware decodes and verifies the bearer token, creating a ClaimsPrincipal, which the authorization middleware uses along with the endpoint metadata to determine whether the request is authorized.

You’ve already seen that you can apply a general authorization requirement by calling RequireAuthorization() on an endpoint or a route group. This is directly equivalent to adding the [Authorize] attribute to a Razor Page or MVC controller action. In fact, you can use the same [Authorize] attribute on an endpoint if you wish, so the following two endpoint definitions are equivalent:

app.MapGet("/", () => "Hello world!").RequireAuthorization();
app.MapGet("/", [Authorize] () => "Hello world!");

If you want to require a specific policy (the "CanCreate" policy, for example), you can pass the policy names to the RequireAuthorization() method the same way you would for the [Authorize] attribute:

app.MapGet("/", () => "Hello world!").RequireAuthorization("CanCreate");
app.MapGet("/", [Authorize("CanCreate")] () => "Hello world!");

Similarly, you can exclude endpoints from authentication requirements using the AllowAnonymous() function or [AllowAnonymous] attribute:

app.MapGet("/", () => "Hello world!").AllowAnonymous();
app.MapGet("/", [AllowAnonymous] () => "Hello world!");

This is a good start, but as you saw in chapter 24, you often need to perform resource-based authorization. For example, in the context of the recipe API, users should be allowed to edit or delete only recipes that they created; they can’t edit someone else’s recipe. That means you need to know details about the resource (the recipe) before determining whether a request is authorized.

Resource-based authorization is essentially the same for minimal API endpoints as for Razor Pages or MVC controllers. You must follow several steps, most of which we covered in chapter 24:

  1. Create an AuthorizationHandler<TRequirement, TResource>, and register it in the DI container, as shown in chapter 24.
  2. Inject the IAuthorizationService into your endpoint handler.
  3. Call IAuthorizationService.AuthorizeAsync(user, resource, policy), passing in the ClaimsPrincipal for the request, the resource to authorize access to, and the policy to apply.

The first step is identical to the process shown in chapter 24, so you can reuse the same authorization handlers whether you’re using Razor Pages, minimal APIs, or both! You can access the IAuthorizationService from a minimal API endpoint using standard dependency injection (DI), which you learned about in chapters 8 and 9.

Listing 25.3 shows an example minimal API endpoint that uses resource-based authorization to protect the “delete” action for a recipe. The IAuthorizationService and HttpContext.User property are injected into the handler method along with the RecipeService. The endpoint then retrieves the recipe and calls AuthorizeAsync() to determine whether to continue with the delete or return a 403 Forbidden response.

Listing 25.3 Using resource authorization to protect a minimal API endpoint

app.MapDelete("recipe/{id}", async (
    int id, RecipeService service,
    IAuthorizationService authService,    #A
    ClaimsPrincipal user) =>    #B
{
    var recipe = await service.GetRecipe(id);    #C
    var result = await authService.AuthorizeAsync(    #D
        user, recipe, "CanManageRecipe");    #D

    if (!result.Succeeded)    #E
    {    #E
        return Results.Forbid();    #E
    }    #E

    await service.DeleteRecipe(id);  #F
    return Results.NoContent();  #F
});

❶ Injected to perform resource-based authorization
❷ The HttpContext.User claims principal for the request
❸ Fetches the recipe to access
❹ Performs resource-based authorization, passing in the user, resource, and the policy name
❺ If authorization failed, returns 403 Forbidden
❻ If authorization succeeded, executes the endpoint as normal

As is common when you start adding functionality, the logic at the heart of the endpoint has become a bit muddled as the endpoint has grown. There are several possible approaches you could take now:

• Do nothing. The logic isn’t that confusing, and this is only one endpoint. This may be a good approach initially but can become problematic if the logic is duplicated across multiple endpoints.
• Pull the authorization out into a filter. As you saw in chapters 5 and 7, endpoint filters can be useful for extracting common cross-cutting concerns, such as validation and authorization. You may find that endpoint filters help reduce the duplication in your endpoint handlers, though this often comes at the expense of additional complexity in the filter itself, as well as a layer of indirection in your handlers. You can see this approach in the source code accompanying this chapter.
• Push the authorization responsibilities down into the domain. Instead of performing the resource-based authorization in your endpoint handlers, you could run the checks inside the domain instead, in the RecipeService in this case. This has advantages, in that it often reduces duplication, keeps your endpoints simpler, and ensures that authorization checks are always applied regardless of how you call the domain methods.
• The downside to this approach is that it may cause your domain/application model to depend directly on ASP.NET Core-specific constructs such as IAuthorizationService. You can work around this by creating a wrapper façade around the IAuthorizationService, but this may also add some complexity. Even if you take this approach, you typically want to apply declarative authorization policies to your endpoints as well to ensure that the endpoint executes only for users who could possibly be authorized.

There’s no single best answer on which approach to take; it will vary depending on what works best for your application. Authentication and authorization are inevitably tricky subjects, so it’s important to consider them early and design your application with security in mind.

Scope-based authorization policies
In section 15.2 I described the role of scopes in the authentication process. When you obtain a bearer token from an identity provider—whether you’re using OpenID Connect or OAuth 2.0—you define the scopes that you wish to retrieve. The user can then choose to grant or deny some or all of those requested scopes. Additionally, the identity provider might allow certain client applications access only to specific scopes. The final access token you receive from the identity provider, which is sent to the API, may have some or none of the requested scopes.

It’s up to the API itself to decide what each scope means and how it should be used to enforce authorization policies. Scopes have no inherent functionality on their own, much like claims, but you can build functionality on top. For example, you can create authorization polices that require a token has the scope "recipe.edit" using

builder.Services.AddAuthorizationBuilder()
.AddPolicy("RecipeEditScope", policy =>
policy.RequireClaim("scope", " recipe.edit "));

This policy could then be applied to any endpoints that edit a recipe.

Another common pattern is to require a specific scope for you to be authorized to make any requests to a given ASP.NET Core app, such as a "receipeApi" scope. This approach can often replace audience validation in bearer token authorization and may be more flexible, as it doesn’t require your identity provider to know the domain at which your API app will be hosted.

Alternatively, you can use scopes to partition your APIs into groups that can only be accessed by certain types of clients. For example, you might have one set of APIs that can be accessed only by internal machine-to-machine clients, another set that can be accessed only by admin users, and another set that can be accessed only by nonadmin users.

Duende has many practical examples of approaches to authorization and authentication using OpenID Connect at http://mng.bz/o1Jp. The examples are geared to IdentityServer users but show many best practices and patterns you can use with identity provider services as well.

That brings us to the end of this chapter on authentication and authorization. We’re not completely done with security, though; in chapter 27 we look at potential security threats and how to mitigate them. But first, in chapter 26 you’ll learn about the logging abstractions in ASP.NET Core and how you can use them to keep tabs on exactly what your app’s up to.

25.7 Summary

In large systems with multiple applications or APIs, you can use an identity provider to centralize authentication and user management. This often reduces the authentication responsibilities of apps, reducing duplication and making it easier to add new user management features.

You should strongly consider using a third-party identity provider service instead of building your own. User management is rarely core to your business, and by delegating responsibility to a third-party you can leave protecting your most vulnerable assets to the experts.

If you do need to build your own identity provider, you can use the IdentityServer or OpenIddict library. These libraries implement the OpenID Connect protocol, adding token generation to a standard ASP.NET Core application. You must build the user management and UI components yourself.

OAuth 2.0 is an authorization protocol that allows a user to delegate authorization for accessing a resource to another application. This standard allows applications to interoperate without compromising on security.

OAuth 2.0 has multiple grant types representing common authorization flows. The authorization code flow with PKCE is the most common interactive grant type when a user initiates an interaction. For machine-only workflows, such as an API calling another API, you can use the client credentials grant type.

OpenID Connect is built on top of OAuth 2.0. It adds conventions, discoverability, and authentication to OAuth 2.0, making it easier to interact with third-party providers and retrieve identity information about a user.

JWTs are the most common bearer token format. They consist of a header, a payload, and a signature, and are base64-encoded. When receiving a JWT you must always verify the signature to ensure that it hasn’t been tampered with.

JWTs are not encrypted, so anyone can read them by default. JWE is a standard that wraps the JWT and encrypts it, protecting the contents. Many identity providers support generating JWEs, and ASP.NET Core supports decoding JWEs automatically.

Bearer token authentication in ASP.NET Core is similar to cookie authentication with traditional web apps. The authentication middleware deserializes the token and validates it. If the token is valid, the middleware creates a ClaimsPrincipal and sets HttpContext.User.

Configure JWT bearer authentication by adding the Microsoft.AspNetCore.Authentication.JwtBearer NuGet Package and calling AddAuthentication().AddJwtBearer() to add the required services to your app.

To generate a JWT for local testing, run dotnet user-jwts create. This configures your API to support JWTs created by the tool and prints a token to the terminal, which you can use for local testing of your API. Add the token to requests in the Authorization header, using the format "Bearer <token>".

Pass additional options to the dotnet user-jwts create command to customize the generated JWT. Add extra claims to the generated JWT using the --claim option, change the sub claim name using --name, or add scope claims to the JWT using --scope.

To enable authorization in Swagger UI, you should add a security scheme to your OpenAPI document. Create an OpenApiSecurityScheme object, and register it with the OpenAPI document by calling AddSecurityDefinition(). Apply it to all the APIs in your app by calling AddSecurityRequirement(), passing in the scheme object.

To add authorization to minimal API endpoints, call RequireAuthorization() or add the [Authorize] attribute to your endpoint handler. This optionally takes the name of an authorization policy to apply, n the same way as you would apply policies to Razor Pages and MVC controllers. You can call RequireAuthorization() on route groups to apply authorization to multiple APIs at the same time.

Override an authorization requirement on an endpoint by calling AllowAnonymous() or by adding the [AllowAnonymous] attribute to an endpoint handler. This removes any authentication requirements from the endpoint, so users can call the endpoint without a bearer token in the request.

Leave a Reply

Your email address will not be published. Required fields are marked *