Securing .Net REST API resources with Duende IdentityServer and MAUI client - Part 2.

Lada Kovjanić 26.12.2024


This topic is divided into three separate articles:
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