Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ASP.NET Core: Building a Robust Authentication and Authorization System using IdentityServer

0.00/5 (No votes)
5 Oct 2020 1  
Build a robust authentication and authorization system using IdentityServer and ASP.NET Core 3.1
In this article, we will see the curious case of Authentication and Authorization using the Identity Server. We will touch upon the fancy terms like OAuth 2.0 and OpenID Connect. My goal in this article is to guide you through all the messy process we have to go through while implementing the authentication and authorization. I will try my best to explain things in plain English.

What the Heck is OAuth 2.0?

OAuth 2.0 is an authorization protocol that allows a user to grant 3rd party apps access to the user’s resources without actually revealing the user’s identity or credentials. The point to notice here is that it’s an authorization protocol and should be only used for authorization scenarios not for authentication workflow.

Image for post

Why OpenID Connect?

OAuth 2.0 is very elegant in solving authorization problems but we needed a solution to handle user authentication use cases. Smart brains came together to build an efficient solution. OpenID Connect is an identity protocol built on the top of the OAuth 2.0 framework.

Say Hi to IdentityServer! 👋

IdentityServer is an OpenID Connect and OAuth 2.0 framework for ASP.NET Core. IdentityServer is an officially certified implementation of OpenID Connect.

As per official documentation of IdentityServer, it has a number of jobs and features:

  • protect your resources
  • authenticate users using a local account store or via an external identity provider
  • provide session management and single sign-on
  • manage and authenticate clients
  • issue identity and access tokens to clients
  • validate tokens

The Hello World Program

In this tutorial, we will build a central authentication system that will handle the authentication request from an MVC and an angular app to access protected resources.

Image for post

The Hello World Program

Image for post

MVC app

Image for post

Angular app

We will set up a central authentication system using IdentityServer. Then we will create an MVC app whose secured pages can be accessed after authentication by the authentication server. We will also create an Angular app that would be able to access secured API after authentication.

Steps Required to Set Up the Central Authentication System — View From 1000 Feets

  • Create MVC ASP.NET Core project
  • Add IdentityServer NuGet packages
  • Add EF Core NuGet packages to store clients and configuration
  • Configure IdentityServer Services in Startup
  • Add IdentityServer pipeline in the middleware
  • Seed Clients and Resources data in the Database
  • Create AccountController to handle Register and Login scenario

Setting Up Authentication Server From Ground Up

  1. Create an empty MVC dotnet core project.
  2. Add required NuGet packages.

    Image for post

    These are a few packages that we need to setup IdentityServer, AspNetIdentity, and lastly EF for persistence.

  3. Let's configure IdentityServer, AspNetIdentity, and EF in the startup file.

    Image for post

    Let’s understand what we did here. Firstly, we configured the entity framework by creating our own DbContext, then we configured AspNetIdentity using the same DbContext. Lastly, we configured IdentityServer by providing our custom ConfiguartionDBContext and PersistedGrantDbContext. All the clients and resource information will be stored via these contexts.

  4. In the middleware pipeline, we just need to add a single line to configure IdentityServer middleware.

    Image for post

    Custom DbContext classes:

    using AuthorizationServer.Models;
    using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore;namespace AuthorizationServer.Persistence
    {
        public class AuthDbContext : IdentityDbContext<AppUser>
        {
            public AuthDbContext(DbContextOptions<AuthDbContext> options)
                : base(options)
            {
            }protected override void OnModelCreating(ModelBuilder builder)
            {
                base.OnModelCreating(builder);
                builder.HasDefaultSchema("Identity");
            }
        }
    }using IdentityServer4.EntityFramework.DbContexts;
    using IdentityServer4.EntityFramework.Options;
    using Microsoft.EntityFrameworkCore;namespace AuthorizationServer.Persistence
    {
        public class AuthConfigurationDbContext : 
                     ConfigurationDbContext<AuthConfigurationDbContext>
        {
            public AuthConfigurationDbContext
                   (DbContextOptions<AuthConfigurationDbContext> options, 
                   ConfigurationStoreOptions storeOptions) : base(options, storeOptions)
            {
            }protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                base.OnModelCreating(modelBuilder);
                modelBuilder.HasDefaultSchema("Identity");
            }
        }
    }using IdentityServer4.EntityFramework.DbContexts;
    using IdentityServer4.EntityFramework.Options;
    using Microsoft.EntityFrameworkCore;namespace AuthorizationServer.Persistence
    {
        public class AuthPersistedGrantDbContext : 
                     PersistedGrantDbContext<AuthPersistedGrantDbContext>
        {
            public AuthPersistedGrantDbContext
                   (DbContextOptions<AuthPersistedGrantDbContext> options, 
                   OperationalStoreOptions storeOptions) : base(options, storeOptions)
            {
            }protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                base.OnModelCreating(modelBuilder);
                modelBuilder.HasDefaultSchema("Identity");
            }
        }
    }

    Nothing fancy, we just created our custom DbContexts so that we can have a custom schema name.

  5. After wiring up of all the DbContexts, we can create a seed class to seed our clients and resources information. We can add all the information directly to the database or we can seed data at the bootup time of the app.

    Image for post

    We need the information of clients that will be authenticated by IdentityServer, in our case, we have two clients, one MVC app and another is an Angular app. We need to specify what resources are allowed for these apps. The Angular app will need access to WeatherAPIs along with OpenId and Profile. Similarly, the MVC app will need access to OpenId and Profile. We need to add WeatherAPI as a scope. URLs of the clients are the URL on which the apps are hosted.

  6. Now we need a controller and a view that could manage registration and login of the users.

    Image for post

    AccountController.cs

That’s it, our AuthenticationServer is ready to handle authentication and authorization requests.

Building an MVC App

Let’s say we have an MVC app and its pages are secured which can only be accessed after authentication.

We need to configure its startup to make it aware of the AuthenticationServer and Authentication mechanism.

Image for post

Startup.cs

AddOpenIdConnect method takes options where we specify the URL of the AuthenticationServer and ClientId, ClientSecret and ResponseType. ClientId should be the same as we configured in the authentication server.

Now create a view home/secret and add [Authorize] attribute to its controller/action method. After that, if a user tries to access the secret view, it will redirect the request to the authentication server, and after providing a valid username and password, the user will be able to see the page.

Setting Up an Angular App

For an Angular app, we need some backend APIs to which the app can communicate. Then we will set up the authentication mechanism in the API. Angular app after authentication sends JWT token to API after that, API will validate that token and claims from the authentication server.

Let’s configure the authentication mechanism in the API startup file.

Image for post

Startup.cs

If you closely look, we have set up the URL of the authentication server and also specified the name of the claim/scope we want in the token.

Coming to our Angular app, we require a solid npm package that can take care of all the boilerplate code that is required to set up the connection between the Angular app and the authentication server.

Let’s “angular-auth-oidc-client” package to our app.

The package is very awesome as it handles most of our workload, we just need to write a configuration function where we specify the URL of the authentication server and claims/scopes that we want and some redirect URLs. That’s it and we are solid.

Image for post

identity.ts

Image for post

app.module.ts

Let’s write some code for login the user and after a successful login, call the API to get the data by sending the token in the header obtained from the authentication server.

Image for post

app.component.ts

Final Words

By using IdentityServer, we can take authentication/authorization logic out of our main app and make it more loosely coupled. IdentityServer helps us to create a central authentication system. In the world of microservices, we do a central authentication system like the one created via IdentityServer.

I have attached screenshots of the code snippet here but you can find the complete source code on GitHub.

Resources

😄 Happy coding!!

History

  • 5th October, 2020: Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here