1. Duende IdentityServer
2. ASP.NET Core 8 Web API with SQL Server db
3. MAUI Client
You can find the complete project in Git repository
This article is a second part of the "Securing .Net REST API resources with Duende IdentityServer and MAUI client" articles. Here we are exploring how to secure resources from the protected API with Duende IdentityServer by implementing OpenID Connect and OAuth2 authentication protocols, and a MAUI client application.
In this article we are going to create LearningAPI ASP.NET Core 8 Web API project. LearningAPI will serve MAUI mobile app with protected data, and to access this data MAUI app will be authorized over the IdentityServer middleware.

Create ASP.NET Core Web API project
When the project is created we should see the file structure in the solution explorer like this:

LearningAPI solution explorer
Now we will add JWT Bearer authentication service to validate JWT signature and authorize requests to our LearningAPI. Add authentication and swagger nuget packages:
•Microsoft.AspNetCore.Authentication.JwtBearer
•Swashbuckle.AspNetCore.Swagger
•Swashbuckle.AspNetCore.SwaggerGen
•Swashbuckle.AspNetCore.SwaggerUI
Open appsettings.json and add ApiSettings and ConnectionString for the database. In this tutorial we are not going to store data to the database, we will use WeatherForecastController and hardcoded data to test authorization.
"ApiSettings": {
"ApiName": "learning.api",
"IdentityServer": "https://localhost:5001",
"Secret": "6429A787-B2C0-4657-DS95-82E8CA06G1G2"
},
"LearningAPIurl": "https://localhost:44313",
"ConnectionStrings": {
"DefaultConnection": "Server=ServerName;Database=LearningApi;User Id=LearningApI;Password=somePassword;MultipleActiveResultSets=true;TrustServerCertificate=True"
}
In Program.cs file add authentication and authorization services
builder.Services.AddAuthentication()
.AddJwtBearer(options =>
{
options.Authority = builder.Configuration.GetSection("ApiSettings").GetValue("IdentityServer");
options.TokenValidationParameters.ValidateAudience = false;
});
builder.Services.AddAuthorization();
Check the API port, right click on the project in the solution explorer, select Properties, go to Debug section and click on – Open debug launch profiles UI, scroll down to the App URL.
In this case port is set to 44313. We need to set in all projects the correct url for testing. If your app is running locally on different port you have to change "LearningAPIurl" value in the appsettings.json in IdentityServer application.

Debug options
Now we have to configure swagger for Learning API, add service to Program.cs file
builder.Services.AddSwaggerGen(options =>
{
options.AddSecurityRequirement(new OpenApiSecurityRequirement() {
{
new OpenApiSecurityScheme {
Reference = new OpenApiReference {
Type = ReferenceType.SecurityScheme,
Id = "oauth2"
},
Scheme = "oauth2",
Name = "oauth2",
In = ParameterLocation.Header
},
new List ()
}
});
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme()
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows()
{
Implicit = new OpenApiOAuthFlow()
{
AuthorizationUrl = new Uri(builder.Configuration.GetSection("ApiSettings").GetValue("IdentityServer") + "/connect/authorize"),
TokenUrl = new Uri(builder.Configuration.GetSection("ApiSettings").GetValue("IdentityServer") + "/connect/token"),
Scopes = new Dictionary()
{
{ "learningAPI", "Learning API" }
}
}
}
});
});
And add swagger, authentication and authorization to the pipeline
app.UseAuthentication();
app.UseAuthorization();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Learning API v1");
c.RoutePrefix = string.Empty;
c.OAuthAppName("Learning API Swagger UI");
c.OAuthClientId(builder.Configuration.GetSection("LearningApiSwaggerSettings").GetValue("ClientId"));
c.OAuthClientSecret(builder.Configuration.GetSection("LearningApiSwaggerSettings").GetValue("Secret"));
c.OAuth2RedirectUrl(builder.Configuration.GetValue("LearningAPIurl") + "/oauth2-redirect.html");
c.OAuthScopes("learningAPI");
c.OAuthUseBasicAuthenticationWithAccessCodeGrant();
});
We will store all Secrets in the appsettings.json file, and don't forget to change it for the release. In the IdentityServer project we have created the client for LearningAPIswagger with Implicit grant type. Now we need to check if the values stored in IdentityServer project -> appsettings.json file -> „LearningApiSwaggerSettings“ section are the same as in LearningAPI project -> appsettings.json file -> „LearningApiSwaggerSettings“.
In IdentityServer project Client for LearningAPI swagger looks like this:
new Client
{
ClientId = _configuration.GetSection("LearningApiSwaggerSettings").GetValue("ClientId"),
ClientName = "Learning API Swagger UI",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
EnableLocalLogin = true,
ClientSecrets =
{
new Secret(_configuration.GetSection("LearningApiSwaggerSettings").GetValue("Secret").Sha256())
},
AllowedCorsOrigins =
{
_configuration.GetValue("LearningAPIurl")
},
RedirectUris = { _configuration.GetValue("LearningAPIurl") + "/oauth2-redirect.html" },
PostLogoutRedirectUris = { },
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"learningAPI"
}
},
We will create a database for our API.
Install nuget packages
• Microsoft.EntityFrameworkCore
• Microsoft.EntityFrameworkCore.SqlServer
• Microsoft.EntityFrameworkCore.Design
• Microsoft.EntityFrameworkCore.Tools
Add new folder Data in LearningAPI project, and add LearningAppContext class if you wish to use database.
Now we will run our projects, right click to project and Configure Startup projects, Set both projects action on start, and make sure IdentityServer is first.

Run projects
When we run our startup projects we will see IdentityServer in one browser and LearningAPI in the other browser

IdentityServer application
Our IdentityServer is running on localhost:5001 and our Learning API is running on localhost:44312. The first page of our Learning API is swagger, we want to test authorization. Click authorize and select learningAPI.

LearningAPI application

LearningAPI authorize
We will be redirected to the IdentityServer login page on localhost:5001/Identity/Account/Login. If we don't have a user we can click on Register and create one. Write credentials and click Log in.

IdentityServer Log in page
If the authorization was successful we will be redirected back to our Learning API start page which is swagger.

Redirected to LearningAPI after successful authorization
And now we are authorized to call our „identity“ protected service, and we should recieve response. If you try to call /identity before authorization you will recieve 401 Unauthorized error.

Calling protected service