Protect an API
In this tutorial, we will explain how to protect a REST API using ASP.NET Core.
The validation of the JWT token passed in the HTTP request typically involves the following steps :
- Signature Verification: The first step is to verify the token's signature to ensure its integrity and authenticity. The signature is generated using a secret key or public/private key pair. The server or verifier needs access to the appropriate key to verify the signature. This step ensures that the token has not been tampered with during transit.
- Expiration Check: The token includes an expiration time (exp) claim that specifies when the token expires. The verifier checks this claim to ensure that the token has not expired. If the current time is after the expiration time, the token is considered invalid and access is denied.
- Issuer Validation: The token includes an issuer claim (iss) that identifies the entity that issued the token. The verifier checks this claim to ensure that the token is issued by a trusted authority. The verifier compares the issuer claim against a list of trusted issuers or a specific issuer that it expects.
- Audience Verification: The token includes an audience claim (aud) that specifies the intended audience or recipient of the token. The verifier checks this claim to ensure that the token is intended for the specific API or application. The verifier compares the audience claim against the expected audience value or a list of valid audiences.
The source code of this project can be found here.
1. Define permission
In SimpleIdServer, a permission consists of defining a scope along with its associated resources.
A scope defines an action, such as read
or delete
, which can be executed by any client that has access to this scope.
A resource defines a REST API, such as shopApi
, that can accept the access token. The resource is identified in the aud
claim of the access token.
In this tutorial, we will configure a read
permission for the shopApi
.
Utilize the administration UI to configure a new permission :
- Open the IdentityServer website at https://localhost:5002.
- On the Scopes screen, click on the
Add scope
button. - Select
API value
and click on next. - Fill-in the form like this and click on the
Save
button to confirm the creation.
Parameter | Value |
---|---|
Name | read |
Description | Read |
- Navigate to the new scope, then select the
API Resources
tab and click on theAdd API resource
button. - Fill-in the form like this and click on the
Add
button to confirm the creation.
Parameter | Value |
---|---|
Name | shopApi |
Value | Shop API |
- In the table, select the
shopApi
resource, and click on the Update button. This will assign the resource to the scope.
2. Create REST.API
Finally, create and configure a REST.API Service.
- Open a command prompt and execute the following commands to create the directory structure for the solution.
mkdir ProtectRESTApiJWT
cd ProtectRESTApiJWT
mkdir src
dotnet new sln -n ProtectRESTApiJWT
- Create a REST.API project named
ShopApi
and install theMicrosoft.AspNetCore.Authentication.JwtBearer
Nuget package.
cd src
dotnet new webapi -n ShopApi
cd ShopApi
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
- Add the
ShopApi
project into your Visual Studio solution.
cd ..\..
dotnet sln add ./src/ShopApi/ShopApi.csproj
- Edit the file
ShopApi\Program.cs
and configure the JWT authentication. Additionally, add an Authorization policy namedread
that checks if the scope is equal toread
.
builder.Services.AddAuthentication(options =>
{
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = "https://localhost:5001/master";
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidAudiences = new List<string>
{
"shopApi"
},
ValidIssuers = new List<string>
{
"https://localhost:5001/master"
}
};
});
builder.Services.AddAuthorization(b =>
{
b.AddPolicy("read", p => p.RequireClaim("scope", "read"));
});
Now your REST.API is configured, the controller or action can be decorate by the AuthorizeAttribute
with the appropriate authorization policy.
For example, the attribute [Authorize("read")]
checks if the JWT contains a scope equal to read
.