1. Duende IdentityServer
1.1. Create IdentityServer project
1.2. Microsoft Identity and SQL Server database
1.3. Swagger
1.4. Duende resources
1.5. Identity UI login/register
2. ASP.NET Core 8 Web API with SQL Server db -> Coming soon
3. MAUI Client -> Coming soon
Ensuring data security is a fundamental feature of every piece of software. Users and applications must be identified, authenticated and authorized for data access in order to avoid compromising the security of resources.
Building mobile applications also implies building an API with database to serve mobile app with data. First thing that crosses my mind is how to protect resources and implement authentication and authorization.
Later we might want to create another frontend web application that will use the same API as a resource of data. To avoid creating authentication functionality across all those applications we can use authentication middleware like IdentityServer.
While developing REST API common practice is implementing API specification such as Swagger which also has to be authenticated as a client on IdentityServer.
To demonstate this we will create the solution that contains three projects:
1. Duende Identity server ASP.NET Core 8 as an authorization middleware
2. Protected ASP.NET Core 8 Web API with SQL Server db that requres authentication for access
3. .NET 8 MAUI Blazor client application that consumes resources from the protected API
1. Duende IdentityServer
1.1. Create IdentityServer project
Duende IdentityServer is an opensource middleware that implements OpenID Connect and OAuth 2.0 in order to secure mobile, native and web applications.
OAuth 2.0 is a standard protocol for implementing different authorization flows for securing applications. This protocol enables users to access protected data by authenticating over authorization middleware which issues access token that is used to authorize and consume service with protected data. In this way user doesn't share his credentials with the resource server, but only with the authorization server over HTTP protocol.
OpenID Connect (OIDC) is an authentication protocol built on top of OAuth 2.0 framework and standardizes authentication for users over OpenId providers – applications where user already has an account, and then authentication information is sent to the protected service with the data and authorization is granted. In simple words users access protected service by signing in to other application (OpenId provider) that checks if user has rights to access this protected service and send that information to it. OpenId provider can be created on our own, such as in this example Duende IdentityServer, or it can be public such as Microsoft, Google, Facebook and others. OIDC also can be used to provide single sign-on.
OpenId and OAuth 2.0 Source
I used to implement IdentityServer4 and other versions as a free open source middleware, but the support ended in December 2022..
The new versions IdentityServer v5 and v6 came out under the new brand Duende, while Enterprise edition is free for development and testing, it has to be paid for production. Duende also has a Community version with the same features as Enterprise edition which seems to be very good free option for individuals and companies with less than 1M USD annual gross, I will be more than happy to pay for it when I succeed to earn that amount of money yearly :-D. I find Community edition very usefull for my projects, easy to setup and migrate from older IdentityServer versions.
Who qualifies for community edition Source
In our case we need to enable users to authorize with email and password, and we will set up different roles as our client application will have at least two different roles (Administrator and User role) that have access to different resources.
The first step is to setup the Identity server.
We will use .NET CLI Templates for Duende IdentityServer, you can check out different templates here Templates
To install templates run this command in command line:
dotnet new install Duende.IdentityServer.Templates
Install Duende templates
After that we will create a root directory „LearningApplication“ for our new solution and a directory „src“ inside LearningApplication.
Create root directory
Now we will create a new project based on IdentityServer empty template which creates minimal IdentityServer project without UI.
Create new project based on IdentityServer empty template
Next we will add IdentityServer project to the solution
Add project to the solution
Now open the solution in IDE, you should see the files as following:
Project files
1.2. Microsoft Identity and SQL Server database
In this example we will implement AspNetCore Identity as a solution for authentication and authorization. We will use Identity's functionality for managing users that are stored in SQL Server database and Identity's login UI functionality.
Install nuget packages:
•Microsoft.AspNetCore.Identity.EntityFrameworkCore
•Microsoft.EntityFrameworkCore.SqlServer
•Microsoft.EntityFrameworkCore.Tools
•Duende.IdentityServer.AspNetIdentity
•Microsoft.AspNetCore.Authentication.JwtBearer
We need to setup our SQL Server database.
In the IdentityServer project add Data folder, and create ApplicationDbContext.cs class inside.
Create ApplicationDbContext
In Data folder create ApplicationUser.cs that will inherit from IdentityUser.
public class ApplicationUser : IdentityUser
{
public string? City { get; set; }
}
Add this code to ApplicationDbContext class:
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions options)
: base(options)
{
}
public DbSet ApplicationUsers { get; set; }
}
Open Program.cs file and add a connection string (don't foget to add DefaultConnection to appsettings.json)
builder.Services.AddDbContext(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
We have to add connection string and other configuration values in appsettings.json
"ApiSettings": {
"ApiName": "id.middleware",
"IdentityServer": "https://localhost:5001",
"Secret": "6429A787-B2C0-4657-DS95-82E8CA06G1G2"
},
"IdMiddlewareSwaggerSettings": {
"ClientId": "id.middleware.swagger",
"Secret": "6389A787-B2C0-4457-DS95-82E8CA06G1G2"
},
"LearningApiSwaggerSettings": {
"ClientId": "learning.api.swagger",
"Secret": "6429A787-B2C0-4657-DS95-82E8CA06G1G2"
},
"LearningMauiSettings": {
"ClientId": "learningMaui",
"Secret": "6429A787-B2C0-4657-DS95-82E8CA06G1G2"
},
"LearningAPIurl": "https://localhost:44313",
"ConnectionStrings": {
"DefaultConnection": "Server=ServerName;Database=LearningApp;User Id= LearningApi;Password=xh1$nchlk0ER;MultipleActiveResultSets=true;TrustServerCertificate=True"
}
Now we need to create user in SQL Server db for authorizing LearningApi application. In this case I've created user „LearningApi“, with password „xh1$nchlk0ER“ with at least select, insert, update, delete, execute grants on default „dbo“ schema in „LearningApp“ database on the server „ServerName“.
Add Identity and setup identity options to Program.cs:
builder.Services.AddIdentity()
.AddEntityFrameworkStores()
.AddDefaultTokenProviders()
.AddDefaultUI();
Create ConfigHelper.cs in the project for sharing configuration and add this code to it
public static class ConfigHelper
{
public static IConfiguration _configuration { get; private set; }
public static void Initialize(IConfiguration configuration)
{
_configuration = configuration;
}
}
Later we will configure resources and scopes and for now we will just create Config.cs class in the project with the code below
public class Config
{
public static IEnumerable IdentityResources =>
new IdentityResource[]
{
};
public static IEnumerable ApiScopes =>
new ApiScope[]
{
};
public static IEnumerable Clients =>
new Client[]
{
};
}
Add IdentityServer configuration to Program.cs
builder.Services.AddIdentityServer(options =>
{
options.UserInteraction = new UserInteractionOptions()
{
LogoutUrl = "/Identity/Account/Logout",
LoginUrl = "/Identity/Account/Login",
LoginReturnUrlParameter = "returnUrl"
};
options.Authentication.CookieLifetime = TimeSpan.FromHours(1);
options.Authentication.CookieSlidingExpiration = false;
})
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddDeveloperSigningCredential()
.AddAspNetIdentity()
.AddInMemoryClients(Config.Clients);
Configure application cookie after calling AddIdentity
builder.Services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
options.LoginPath = "/Identity/Account/Login";
options.AccessDeniedPath = "/Identity/Account/AccessDenied";
options.SlidingExpiration = true;
});
Add Controllers, and razorPages to Program.cs file
builder.Services.AddControllers();
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
Add Authentication, Authorization, IdentityServer and other things to the pipeline
app.UseRouting();
app.MapControllers();
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
Bring the Identity to the database with migration. Open Package Manager Console and execute command
Add-Migration Initial
After migration is done you will see generated Initial migration sql script.
Now we can bring those changes to our database, in Package Manager Console execute command
Update-Database
If you open your LearningApp database in Sql Server Management you will see that new tables for authorization are created
SQL Server database
1.3. SWAGGER
Common practice while creating API project is implementing API specification such as Swagger. If we are planning to create API endpoints for managing users, roles in our IsentityServer application etc. we should add Swagger to our project.
In new version .NET9 Swagger will no longer be supported and we will have to change it to OpenAPI or other.
Open “Manage Nuget Packages” and install “Swashbuckle.AspNetCore.SwaggerUI” and “Swashbuckle.AspNetCore.SwaggerGen” packages.
Add this code to Program.cs
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
add this to the pipeline
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Identity Server API v1");
c.RoutePrefix = string.Empty;
c.OAuthAppName("Identity server API Swagger UI");
c.OAuthClientId(builder.Configuration.GetSection("IdMiddlewareSwaggerSettings").GetValue("ClientId"));
c.OAuthClientSecret(builder.Configuration.GetSection("IdMiddlewareSwaggerSettings").GetValue("Secret"));
c.OAuth2RedirectUrl(builder.Configuration.GetSection("ApiSettings").GetValue("IdentityServer") + "/oauth2-redirect.html");
c.OAuthScopes("id.middleware");
c.OAuthUseBasicAuthenticationWithAccessCodeGrant();
});
1.4. Duende resources
There are three types of resources – IdentityResources, ApiScopes and ApiResources.
During the authentication process client app will ask IdentityServer for a set of scopes:
•Identity scopes with information about the user (Id token) and
•Access scopes with information about what scopes needs to be accessed to (Access token).
We have to define Identity resources, in this scenario we will use standardized resources openid, profile and email scope.
Earlier we have created Config.cs file in the project, and now we will define resources there
public static IEnumerable IdentityResources =>
[
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email()
];
Also we need to define Scope values that represent available resources, in our case we want to protect our Learning API so the scope will be learningAPI.
We will define Clients in the Config.cs file:
•Id Middleware Swagger,
•Learning API,
•Learning Api Swagger,
•LearningMaui
Full version of our Config.cs will look like this
public class Config
{
private static IConfiguration _configuration = ConfigHelper._configuration;
public static IEnumerable IdentityResources =>
[
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email()
];
public static IEnumerable ApiScopes =>
[
new ApiScope("learningAPI", "Learn API"){
UserClaims =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email
}
}
];
public static IEnumerable Clients =>
[
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"
}
},
new Client
{
ClientId = _configuration.GetSection("LearningMauiSettings").GetValue("ClientId"),
ClientName = "Learning Maui Application",
AllowedGrantTypes = GrantTypes.Code,
AllowAccessTokensViaBrowser = true,
RequireConsent = false,
ClientSecrets =
{
new Secret(_configuration.GetSection("LearningMauiSettings").GetValue("Secret").Sha256())
},
RedirectUris = { "myapp://" },
PostLogoutRedirectUris = { "myapp://" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"learningAPI"
},
AllowOfflineAccess = true
},
new Client
{
ClientId = _configuration.GetSection("IdMiddlewareSwaggerSettings").GetValue("ClientId"),
ClientName = "Identity server API Swagger UI",AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
EnableLocalLogin = true,
ClientSecrets =
{
new Secret( _configuration.GetSection("IdMiddlewareSwaggerSettings").GetValue("Secret").Sha256())
},
AllowedCorsOrigins =
{
_configuration.GetSection("ApiSettings").GetValue("IdentityServer"),
},
RedirectUris = {
_configuration.GetSection("ApiSettings").GetValue("IdentityServer") + "/oauth2-redirect.html"
},
PostLogoutRedirectUris = {
},
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email
}
}
];
}
1.5. Identity UI login/register
We are using ASP.NET Core Identity for managing users, roles, paswords, tokens and it also supports UI login functionality.
To add Identity UI login pages, we will use scaffolded items, right click on project -> Add -> New scaffolded item
Add scaffolded items
In the modal select Identity, then click Add.
Add scaffolded item Identity
This might take a while to get all the templates. When the modal with list of templates appears, select all the layouts that you need and select dbContext. Scaffolding will take a while.
Scaffold Login, Logout, Register, SetPassword pages
After scaffold is done and we expand the file solution explorer we will see that the new folder Areas is added with containing pages that we have selected.
We will use ApplicationUser class, so in these Identity pagse we need to replace IdentityUser class with ApplicationUser.
Scaffolded Identity files
Now we have finished preparing our IdentityServer application. In the next article we are going to explore how to create ASP.NET Core 8 API with protected resources.