Getting started with ASP.NET Core IdentityServer 4 and Angular

Posted on by Roger Versluis.

Introduction

We will setup IdentityServer 4 in SQL Server and create a simple Angular registration page.

IdentityServer 4

IdentityServer 4 is a free, open source OpenID Connect and OAuth 2.0 framework for ASP.NET Core. Founded and maintained by Dominick Baier and Brock Allen, IdentityServer4 incorporates all the protocol implementations and extensibility points needed to integrate token-based authentication, single-sign-on and API access control in your applications. IdentityServer4 is officially certified by the OpenID Foundation and thus spec-compliant and interoperable. It is part of the .NET Foundation.

IdentityServer is fully integrated in the .NET Core framework and is very easy to setup, customize and use. Because of its versatility it’s a perfect candiate for any project requiring authentication and authorization.

Installing SQL Server

To get started we will need some sort of database backend. In this article we will assume you have SQL Server 2017 Express installed. Other databases like PostgreSQL, SQLite, MariaDB or other version of SQL Server will work too.

Adding the DataContext

Create a new folder in the root of the project called Data and add a file called DataContext.cs with the following content:

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace server.Data
{
    public class DataContext : IdentityDbContext
    {
        public DataContext(DbContextOptions<DataContext> options)
            : base(options)
        {
        }
    }
}

Here we initialize a new DataContext and by implementing IdentityDbContext we will inherit all the default tables and entities associated with IdentityServer.

Open Startup.cs and add the following lines to ConfigureServices above services.AddMvc():

services.AddDbContext<DataContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Default")));
services.AddIdentity<IdentityUser, IdentityRole>().AddEntityFrameworkStores<DataContext>();

This will initialize our project to use SQL Server and IdentityServer.

Open appsettings.json and add a section to store your connection string:

"ConnectionStrings": {
  "Default": "Data Source=.\\SQLEXPRESS;Initial Catalog=AspNetAngularApp;Integrated Security=True;MultipleActiveResultSets=True;"
}

Make sure to replace your connection string with one that works for you.

To generate the database schema we will use EF Core Migrations. Open the Package Manager Console in Visual Studio and execute the following command:

Add-Migration Identity

This will generate your first migration with all the IdentityServer entities included. Now execute

Update-Database

And the database with tables will be created.

User Registration

To allow users to register we’ll create a simple registration page.

In Visual Studio create a new Controller called AuthenticationController.cs and give it the following content:

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Threading.Tasks;

namespace server.Controllers
{
    [Route("Api/Authentication")]
    public class AuthenticationController : Controller
    {
        private readonly UserManager<IdentityUser> _userManager;

        public AuthenticationController(UserManager<IdentityUser> userManager)
        {
            _userManager = userManager;
        }

        [HttpPost("Register")]
        public async Task<IActionResult> Register([FromBody]UsersRegisterRequest request)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest();
            }

            var user = new IdentityUser
            {
                UserName = request.UserName
            };

            var result = await _userManager.CreateAsync(user, request.Password);

            if (result.Succeeded)
            {
                return Ok();
            }

            return BadRequest(result.Errors);
        }
    }

    public class UsersRegisterRequest
    {
        public String UserName { get; set; }
        public String Password { get; set; }
    }
}

The Register method will use the IdentityServer UserManager to validate and register our new user. If there is a problem we return a BadRequest with a list of validation errors.

Adding a registration page

Open VSCode and scaffold a new RegisterComponent:

ng g c register

Add the following content to register.component.html:

<div class="login-wrapper">
  <form class="login" clrForm #loginForm (ngSubmit)="onSubmit()" clrLayout="vertical">
      <section class="title">
          <h3 class="welcome">Welcome to</h3>
          Your angular app
          <h5 class="hint">Create a new account</h5>
      </section>
      <div class="login-group">
          <clr-input-container>
              <input type="text" name="username" clrInput placeholder="User name" [(ngModel)]="username" />
          </clr-input-container>
          <clr-password-container>
              <input type="password" name="password" clrPassword placeholder="Password" [(ngModel)]="password" />
          </clr-password-container>
          <div class="error active" *ngFor="let error of errors">
              {{error}}
          </div>
          <button type="submit" class="btn btn-primary" [clrLoading]="loginState">NEXT</button>
          <a href="" class="signup">Log in</a>
      </div>
  </form>
</div>

and register.component.ts:

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { ClrLoadingState } from '@clr/angular';
import { HttpErrorResponse } from '@angular/common/http';
import { AuthenticationService } from '../authentication.service';

@Component({
  selector: 'app-register',
  templateUrl: './register.component.html',
  styleUrls: ['./register.component.scss']
})
export class RegisterComponent implements OnInit {

  public username: string;
  public password: string;
  public errors: string[];
  public loginState = ClrLoadingState.DEFAULT;
  public returnUrl: string;

  constructor(private authentication: AuthenticationService, private router: Router) { }

  ngOnInit() {

  }

  public onSubmit() {
    this.loginState = ClrLoadingState.LOADING;
    this.errors = [];
    this.authentication.register(this.username, this.password).subscribe(() => {
      this.loginState = ClrLoadingState.SUCCESS;
      this.router.navigate(['/']);
    }, (error: HttpErrorResponse) => {
      if (error.error instanceof Array) {
        this.errors = error.error.map(m => m.description);
      }
      else {
        this.errors = [error.error.message];
      }
      this.loginState = ClrLoadingState.ERROR;
    });
  }
}

Generate a new AuthenticationService:

ng g s authentication

And give it the following content:

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class AuthenticationService {

  constructor(private http: HttpClient) { }

  public register(username: string, password: string): Observable<void> {
    return this.http.post<void>(`/api/authentication/register`, { username, password });
  }
}

Add the following route to the beginning of your routes array in app-routing.module.ts

  {
    path: 'register',
    component: RegisterComponent
  },

Because the registration page is using an Angular Form we have to add FormsModule to our NgModule.imports array in app.module.ts:

  imports: [
    BrowserModule,
    AppRoutingModule,
    ClarityModule,
    BrowserAnimationsModule,
    HttpClientModule,
    FormsModule
  ],

Run the Visual Studio project, run ng serve in VSCode and navigate to http://localhost:4200/register.

Try entering a short password and you will be presented with validation errors, but correcting those errors will register you as a user through IdentityServer!

Conclusion

We setup the ASP.NET Core IdentityServer with default options and hooked up an Angular registration page. In the next article we will be adding authentication and auth guards to validate users with JWT.