Originally published by Didin J at djamware.com

The comprehensive step by step tutorial on building Web Application Authentication using ASP.NET Core Web API, Microsoft SQL Server, and Angular 7. We will create our own Microsoft SQL Server Database and Tables (User and Book). Password in the User table will be encrypted using salted HMACSHA512. The authentication flow describes a sequence diagram below.

Table of contents


  • Create Microsoft SQL Server Database and Tables
  • Create and Configure a new ASP.NET Core Web API Application
  • Generate Models and Context from Microsoft SQL Server Database
  • Create DTO (Data Transfer Object) for Request Body and Response
  • Create Helpers Class for Mapping DTO with Model Classes
  • Create Controller for Books Data
  • Create a Repository for Authentication
  • Create Controller for Authentication
  • Test Secure API using Postman
  • Install or Update Angular 7 CLI and Create Application
  • Add Routes for Navigation between Angular Pages/Component
  • Create a custom Angular 7 HttpInterceptor
  • Create Services for Accessing Book and Authentication API
  • Display List of Book using Angular 7 Material
  • Create a Login and Register Page
  • Run and Test the ASP Net Core, SQL Server, and Angular 7: Web App Authentication

The book API is secure, so only authorized user can access that endpoint otherwise it will return 401 (Unauthorized) error and redirect the user to the login page. Next, the user will log in using email and password then after successful login (credentials validated successfully), it will response success status with JWT token. JWT token will be used to access book endpoint as Authorization headers. So, the token should be store in Angular 7 app and all access to book API securely and seamlessly using Angular 7 Http Interceptors.

The following tools, frameworks, package, and modules are required for this tutorial:

  1. ASP.NET Core SDK
  2. Visual Studio Code
  3. Node.js
  4. Angular 7
  5. SQL Server 2017 Express
  6. Postman
  7. Command prompt (CMD)

We assume that you already install all above required tools. Now, we have to check that DotNet SDK already installed and added to the path. Open command prompt then type this command to make sure DotNet installed and runnable via command prompt.

dotnet --version

You will get this result.

2.1.403

We are using the DotNet Framework SDK version 2.1.403. That's mean you're ready to move to the main steps.

Create Microsoft SQL Server Database and Tables

We will create a Database for this web application with User and Book tables. For that, open Microsoft SQL Server Management Studio then connects to your local SQL Server (we are using SQL Express 2017). Right-click Database on the left pane then clicks New Database menu.

Fill Database Name (we name it "Book Store"), leave owner as default because we will use sa user then click OK button. Next, expand the BookStore Database then right-click tables -> table. Fill the table as below then save as `TblUser`.

Or simply just run this SQL commands.

USE [BookStore]
GO

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[TblUser](
    [UserID] [int] IDENTITY(1,1) NOT NULL,
    [FullName] varchar NULL,
    [Email] varchar NULL,
    [Password] varbinary NULL,
    [Salt] varbinary NULL,
 CONSTRAINT [PK_TblUser] PRIMARY KEY CLUSTERED
(
    [UserID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

Next, create a Book table using these SQL commands.

USE [BookStore]
GO

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[TblBook](
    [BookID] [int] IDENTITY(1,1) NOT NULL,
    [ISBN] varchar NULL,
    [Title] varchar NULL,
    [Author] varchar NULL,
    [Description] varchar NULL,
    [Publisher] varchar NULL,
    [PublishedYear] [int] NULL,
    [Price] [decimal](18, 0) NULL,
 CONSTRAINT [PK_TblBook] PRIMARY KEY CLUSTERED
(
    [BookID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

Create and Configure a new ASP.NET Core Web API Application

A slightly different ASP.NET Core application creation, we will use the command prompt. Of course, you can use the more easiest way when using Microsoft Visual Studio. In the command prompt go to your projects folder then type this command.

dotnet  new webapi –o AspNetAngularAuth –n AspNetAngularAuth

You will the output like below in the Command Prompt.

The template “ASP.NET Core Web API” was created successfully.

Processing post-creation actions…
Running ‘dotnet restore’ on AspNetAngularAuth\AspNetAngularAuth.csproj…
  Restoring packages for C:\Users\DIDIN\Projects\AspNetAngularAuth\AspNetAngularAuth.csproj…
  Generating MSBuild file C:\Users\DIDIN\Projects\AspNetAngularAuth\obj\AspNetAngularAuth.csproj.nuget.g.props.
  Generating MSBuild file C:\Users\DIDIN\Projects\AspNetAngularAuth\obj\AspNetAngularAuth.csproj.nuget.g.targets.
  Restore completed in 5.8 sec for C:\Users\DIDIN\Projects\AspNetAngularAuth\AspNetAngularAuth.csproj.

Restore succeeded.

That’s mean, you can open directly as a folder from Visual Studio Code as well as CSharp Project/Solution from Microsoft Visual Studio. Or you can call this new ASP.NET Core Web API Project by type this command.

cd AspNetAngularAuth
code .

The project will be opened by Visual Studio Code. Next, open terminal from the menu or press Ctrl+Shift+. Run these commands to installs all required packages.</p><pre class="ql-syntax" spellcheck="false">dotnet add package Microsoft.EntityFrameworkCore.Tools -v 2.1.2 dotnet add package Microsoft.EntityFrameworkCore.SqlServer -v 2.1.2 dotnet add package Microsoft.EntityFrameworkCore.SqlServer.Design -v 1.1.6 dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design -v 2.1.5 dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Tools -v 2.0.4 dotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection -v 5.0.1 </pre><p>If you see a notification like below, just click Restore button.</p><p><img src="https://s3-ap-southeast-1.amazonaws.com/djamblog/article-270119063810.png"></p><p>Now, you will have these packages with the right version installed shown in <code>AspNetAngularAuth.csproj</code> file.</p><pre class="ql-syntax" spellcheck="false"> &lt;ItemGroup&gt; &nbsp; &lt;PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="5.0.1" /&gt; &nbsp; &lt;PackageReference Include="Microsoft.AspNetCore.App" /&gt; &nbsp; &lt;PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.1.2" PrivateAssets="All" /&gt; &nbsp; &lt;PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.1.2" /&gt; &nbsp; &lt;PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.6" /&gt; &nbsp; &lt;PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.1.2"&gt; &nbsp; &nbsp; &lt;IncludeAssets&gt;runtime; build; native; contentfiles; analyzers&lt;/IncludeAssets&gt; &nbsp; &nbsp; &lt;PrivateAssets&gt;all&lt;/PrivateAssets&gt; &nbsp; &lt;/PackageReference&gt; &nbsp; &lt;PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.1.5" /&gt; &nbsp; &lt;PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.4" /&gt; &lt;/ItemGroup&gt; </pre><p>Next, build the DotNet application to make sure there's no error in package dependencies.</p><pre class="ql-syntax" spellcheck="false">dotnet build </pre><p>You must see this output when everything on the right path.</p><pre class="ql-syntax" spellcheck="false">Build succeeded. &nbsp; &nbsp; 0 Warning(s) &nbsp; &nbsp; 0 Error(s) </pre><p>Next, we have to configure connections to Microsoft SQL Server Database. First, open and edit <code>appsettings.json</code> then add these lines before loggingJSON object.</p><pre class="ql-syntax" spellcheck="false"> "ConnectionStrings": { &nbsp; "SQLConnection": "Server=.;Database=BookStore;Trusted_Connection=True;User Id=sa;Password=q;Integrated Security=false;MultipleActiveResultSets=true" }, </pre><p>Add this configuration for Token secret.</p><pre class="ql-syntax" spellcheck="false">"AppSettings": { &nbsp;&nbsp; &nbsp;"Token": "MySecretTokenForJwt" }, </pre><p>Open and editStartup.csthen add this line insideConfigureServicesbracket.</p><pre class="ql-syntax" spellcheck="false">services.AddDbContext&lt;BookStoreContext&gt;(options =&gt; options.UseSqlServer(Configuration.GetConnectionString("SQLConnection"))); </pre><p>Don't worry <code>BookStoreContext</code> will be added later when generate context and data models. For now, you can comment out this line then uncomment after Models and Context generation and set the imports to it. Next, add this line to enable CORS after the above line.</p><pre class="ql-syntax" spellcheck="false"> services.AddCors(); </pre><p>Next, add these lines to configure JWT Authentication token.</p><pre class="ql-syntax" spellcheck="false">services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) &nbsp; .AddJwtBearer(options =&gt; { &nbsp; &nbsp; &nbsp; options.TokenValidationParameters = new TokenValidationParameters &nbsp; &nbsp; &nbsp; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ValidateIssuerSigningKey = true, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .GetBytes(Configuration.GetSection("AppSettings:Token").Value)), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ValidateIssuer = false, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ValidateAudience = false &nbsp; &nbsp; &nbsp; }; &nbsp; }); </pre><p>Also, add these lines inside <code>Configure</code> bracket.</p><pre class="ql-syntax" spellcheck="false"> app.UseCors(x =&gt; x.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()); app.UseAuthentication(); </pre><p>Don't forget to import the library or module that added in the Configuration.</p><h2><br></h2><h2><strong>Generate Models and Context from Microsoft SQL Server Database</strong></h2><p>We will use the Code Generation Tools package to generate all models that represent all tables in theBookStoreDatabase including theBookStoreContext. Run this command to generate the models and their context.</p><pre class="ql-syntax" spellcheck="false">dotnet ef dbcontext scaffold "Server=.;Database=BookStore;Trusted_Connection=True;User Id=sa;Password=q;Integrated Security=false;" Microsoft.EntityFrameworkCore.SqlServer -o Models </pre><p>You will see the generated Models and Context inside the Models folder.</p><p><img src="https://s3-ap-southeast-1.amazonaws.com/djamblog/article-270419111940.png"></p><p>Remove unused lines (without commenting out) to clear the error in BookStoreContext. If you're not using Models and Context generator, the contents of Models/TblUser.cs` looks like these.

using System;
using System.Collections.Generic;

namespace AspNetAngularAuth.Models
{
    public partial class TblUser
    {
        public int UserId { get; set; }
        public string FullName { get; set; }
        public string Email { get; set; }
        public byte[] Password { get; set; }
        public byte[] Salt { get; set; }
    }
}

The Models/TblBook.cs look like these.

using System;
using System.Collections.Generic;

namespace AspNetAngularAuth.Models
{
    public partial class TblBook
    {
        public int BookId { get; set; }
        public string Isbn { get; set; }
        public string Title { get; set; }
        public string Author { get; set; }
        public string Description { get; set; }
        public string Publisher { get; set; }
        public int? PublishedYear { get; set; }
        public decimal? Price { get; set; }
    }
}

And the Models/BookStoreContext.cs looks like these.

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;

namespace AspNetAngularAuth.Models
{
    public partial class BookStoreContext : DbContext
    {
        public BookStoreContext()
        {
        }

        public BookStoreContext(DbContextOptions<BookStoreContext> options)
            : base(options)
        {
        }

        public virtual DbSet<TblBook> TblBook { get; set; }
        public virtual DbSet<TblUser> TblUser { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
               optionsBuilder.UseSqlServer(“Server=.;Database=BookStore;Trusted_Connection=True;User Id=sa;Password=q;Integrated Security=false;”);
            }
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<TblBook>(entity =>
            {
                entity.HasKey(e => e.BookId);

                entity.Property(e => e.BookId).HasColumnName(“BookID”);

                entity.Property(e => e.Author)
                    .HasMaxLength(50)
                    .IsUnicode(false);

                entity.Property(e => e.Description)
                    .HasMaxLength(200)
                    .IsUnicode(false);

                entity.Property(e => e.Isbn)
                    .HasColumnName(“ISBN”)
                    .HasMaxLength(50)
                    .IsUnicode(false);

                entity.Property(e => e.Price).HasColumnType(“decimal(18, 0)”);

                entity.Property(e => e.Publisher)
                    .HasMaxLength(50)
                    .IsUnicode(false);

                entity.Property(e => e.Title)
                    .HasMaxLength(100)
                    .IsUnicode(false);
            });

            modelBuilder.Entity<TblUser>(entity =>
            {
                entity.HasKey(e => e.UserId);

                entity.Property(e => e.UserId).HasColumnName(“UserID”);

                entity.Property(e => e.Email)
                    .HasMaxLength(50)
                    .IsUnicode(false);

                entity.Property(e => e.FullName)
                    .HasMaxLength(50)
                    .IsUnicode(false);

                entity.Property(e => e.Password).HasMaxLength(128);

                entity.Property(e => e.Salt).HasMaxLength(128);
            });
        }
    }
}

Create DTO (Data Transfer Object) for Request Body and Response

To specify the request body and response fields, we will use a Data Transfer Object (DTO). For that, create a new DTOs folder and BookListDto.cs, LoginDto.cs, RegisterDto.cs files inside that folder. Next, open and edit Dtos/LoginDto.cs then replace all CSharp codes with these.

using System.ComponentModel.DataAnnotations;

namespace AspNetAngularAuth.Dtos
{
    public class LoginDto
    {
        [Required]
        public string Email { get; set; }
        [Required]
        public string Password { get; set; }
    }
}

Next, open and edit Dtos/RegisterDto.cs then replace all CSharp codes with these.

using System.ComponentModel.DataAnnotations;

namespace AspNetAngularAuth.Dtos
{
    public class RegisterDto
    {
        [Required]
        [StringLength(50, MinimumLength = 3, ErrorMessage = “Email must be at least 3 characters”)]
        public string FullName { get; set; }
        [Required]
        [StringLength(50, MinimumLength = 3, ErrorMessage = “Email must be at least 3 characters”)]
        public string Email { get; set; }
        [Required]
        [StringLength(64, MinimumLength = 8, ErrorMessage = “You must provide password between 8 and 20 characters”)]
        public string Password { get; set; }
    }
}


Create Helpers Class for Mapping DTO with Model Classes

To create a helper for mapping DTO to Model classes, create the folder Helpers in the root of the project folder then create a file AutoMapperProfile.cs inside that new folder. Open and edit this new file then replace all codes with this.

using AspNetAngularAuth.Dtos;
using AspNetAngularAuth.Models;
using AutoMapper;

namespace AspNetAngularAuth.Helpers
{
    public class AutoMapperProfile: Profile
    {
        public AutoMapperProfile()
        {
            CreateMap<TblBook, BookListDto>();
            CreateMap<LoginDto, TblUser>();
            CreateMap<RegisterDto, TblUser>();
        }
    }
}

Next, open and edit again Startup.cs then add this line inside ConfigureServices.

services.AddAutoMapper();

Don’t forget to auto import the library in ConfigureServices.


Create Controller for Books Data

To get a list of the book using API, we have to create a CSharp file Controllers/BookController.cs. Open and edit Controllers/BookController.cs then replace all CSharp codes with these.

using System.Threading.Tasks;
using AspNetAngularAuth.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace AspNetAngularAuth.Controllers
{
    [Authorize]
    [Route(“api/[controller]”)]
    [ApiController]
    public class BookController: ControllerBase
    {
        private readonly BookStoreContext _context;
        public BookController(BookStoreContext context)
        {
            _context = context;
        }

        [HttpGet]
        public async Task<IActionResult> GetBooks()
        {
            var data = await _context.TblBook.ToListAsync();
            return Ok(data);
        }
    }
}

There’s an [Authorized] annotation which means this API resource is secured. So, only authorized user can access this API endpoint.

Create a Repository for Authentication

Before creating a file for Authentication repository, we have to create a repositories folder first with the name Repositories in the root of the project folder. Next, create a file with the name Repositories/AuthRepository.cs. Open and edit that file then replace all CSharp codes with these.

using System.Threading.Tasks;
using AspNetAngularAuth.Models;
using Microsoft.EntityFrameworkCore;

namespace AspNetAngularAuth.Repositories
{
    public class AuthRepository: IAuthRepository
    {
        private readonly BookStoreContext _context;

        public AuthRepository(BookStoreContext context)
        {
            _context = context;
        }

        public async Task<TblUser> Login(string email, string password)
        {
            var user = await _context.TblUser.FirstOrDefaultAsync(x => x.Email == email);
            if (user == null)
                return null;

            if (!VerifyPasswordHash(password, user.Password, user.Salt))
                return null;

            return user; // auth successful
        }

        public async Task<TblUser> Register(TblUser user, string password)
        {
            byte[] passwordHash, salt;
            CreatePasswordHash(password, out passwordHash, out salt);
            user.Password = passwordHash;
            user.Salt = salt;

            await _context.TblUser.AddAsync(user);
            await _context.SaveChangesAsync();

            return user;
        }

        private bool VerifyPasswordHash(string password, byte[] passwordHash, byte[] salt)
        {
            using (var hmac = new System.Security.Cryptography.HMACSHA512(salt))
            {
                var computedHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
                for (int i = 0; i < computedHash.Length; i++)
                {
                    if (computedHash[i] != passwordHash[i]) return false;
                }
            }
            return true;
        }

        private void CreatePasswordHash(string password, out byte[] passwordHash, out byte[] salt)
        {
            using (var hmac = new System.Security.Cryptography.HMACSHA512())
            {
                salt = hmac.Key;
                passwordHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
            }
        }

        public async Task<bool> UserExists(string Username)
        {
            if (await _context.TblUser.AnyAsync(x => x.Email == Username))
                return true;
            return false;
        }
    }
}

Next, create an interface file with the name IAuthRepository.cs then replace all CSharp code with these.

using System.Threading.Tasks;
using AspNetAngularAuth.Models;

namespace AspNetAngularAuth.Repositories
{
     public interface IAuthRepository
     {
             Task<TblUser> Register(TblUser user, string password);
             Task<TblUser> Login(string username, string password);
             Task<bool> UserExists(string username);
     }
}

Next, open and edit again Startup.cs then add this line inside ConfigureServices bracket.

services.AddScoped<IAuthRepository, AuthRepository>();

Also, import the required classes and libraries.

Create Controller for Authentication

Now, we have to create an endpoint for user login and register. Create a CSharp file with the name Controllers/AuthController.cs then open that file and replace all CSharp codes with these.

using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using AspNetAngularAuth.Dtos;
using AspNetAngularAuth.Models;
using AspNetAngularAuth.Repositories;
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;

namespace AspNetAngularAuth.Controllers
{
    [Route(“api/[controller]”)]
    [ApiController]
    public class AuthController: ControllerBase
    {
        private readonly IAuthRepository _repo;
        private readonly IConfiguration _config;
        private readonly IMapper _mapper;

        public AuthController(IAuthRepository repo, IConfiguration config, IMapper mapper)
        {
            _mapper = mapper;
            _config = config;
            _repo = repo;
        }

        [HttpPost(“register”)]
        public async Task<IActionResult> Register(RegisterDto registerDto)
        {
            registerDto.Email = registerDto.Email.ToLower();
            if (await _repo.UserExists(registerDto.Email))
                return BadRequest(“Email already exists”);

            var userToCreate = _mapper.Map<TblUser>(registerDto);
            var createdUser = await _repo.Register(userToCreate, registerDto.Password);
            return StatusCode(201, new { email = createdUser.Email, fullname = createdUser.FullName });
        }

        [HttpPost(“login”)]
        public async Task<IActionResult> Login(LoginDto loginDto)
        {
            var userFromRepo = await _repo.Login(loginDto.Email.ToLower(), loginDto.Password);
            if (userFromRepo == null)
                return Unauthorized();

            var claims = new[]
            {
                new Claim(ClaimTypes.NameIdentifier, userFromRepo.UserId.ToString()),
                new Claim(ClaimTypes.Name, userFromRepo.Email)
            };

            var key = new SymmetricSecurityKey(Encoding.UTF8
                .GetBytes(_config.GetSection(“AppSettings:Token”).Value));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(claims),
                Expires = DateTime.Now.AddDays(1),
                SigningCredentials = creds
            };

            var tokenHandler = new JwtSecurityTokenHandler();
            var token = tokenHandler.CreateToken(tokenDescriptor);

            return Ok(new {token = tokenHandler.WriteToken(token), email = userFromRepo.Email, fullname = userFromRepo.FullName});
        }
    }
}


Test Secure API using Postman

Now, we have to run the ASP.NET Core Web API from the Terminal by typing this command.

dotnet watch run

Watch keyword is the additional command for monitoring any change in the codes then reloading the ASP.NET Core Web API application. Next, open or run the Postman application. Use the GET method and fill the right column after the GET method with localhost:5000/api/Book, Headers key with Content-Type, Headers value with application/json. You will see 401 response after sending the request.

That’s mean, only authorized user can access the /api/Book endpoint. Next, we have to register a user first before login and get the authentication token. In the Postman, change the Method to POST, change the address to /api/auth/Register and fill the body (raw) with this JSON data.


Next, login using above successfully registered email and password. Change the address to /api/auth/Login and body (raw) as below.

Now, you can get the Token from the response and put as Authorization -> Bearer Token value to GET book data.

Install or Update Angular 7 CLI and Create Application

Before installing the Angular 7 CLI, make sure you have installed Node.js https://nodejs.organd can open Node.js command prompt. Next, open the Node.js command prompt then type this command to install Angular 7 CLI.

npm install -g @angular/cli

Next, create an Angular 7 application by typing this command in the root of ASP.NET Core application/project directory.

ng new client

Where client is the name of the Angular 7 application. You can specify your own name, we like to name it client because it’s put inside ASP.NET Core Project directory. If there’s a question, we fill them with Y and SCSS. Next, go to the newly created Angular 7 application.

cd client

Run the Angular 7 application for the first time.

ng serve

Now, go to localhost:4200 and you should see this page.


Add Routes for Navigation between Angular Pages/Component

On the previous steps, we have to add Angular 7 Routes when answering the questions. Now, we just added the required pages for CRUD (Create, Read, Update, Delete) Supplier data. Type this commands to add the Angular 7 components or pages.

ng g component book
ng g component auth/login
ng g component auth/register

Open src/app/app.module.ts then you will see those components imported and declared in @NgModule declarations. Next, open and edit src/app/app-routing.module.ts then add these imports.

import { BookComponent } from ‘./book/book.component’;
import { LoginComponent } from ‘./auth/login/login.component’;
import { RegisterComponent } from ‘./auth/register/register.component’;

Add these arrays to the existing routes constant.

const routes: Routes = [
  {
    path: ‘book’,
    component: BookComponent,
    data: { title: ‘List of Books’ }
  },
  {
    path: ‘login’,
    component: LoginComponent,
    data: { title: ‘Login’ }
  },
  {
    path: ‘register’,
    component: RegisterComponent,
    data: { title: ‘Register’ }
  }
];

Open and edit src/app/app.component.html and you will see the existing router outlet. Next, modify this HTML page to fit the CRUD page.

<div style=“text-align:center”>
  <h1>
    Welcome to {{ title }}!
  </h1>
  <img width=“150” alt=“Angular Logo” src=“data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==”>
</div>
<div class=“container”>
  <router-outlet></router-outlet>
</div>

Open and edit src/app/app.component.scss then replace all SCSS codes with this.

.container {
  padding: 20px;
}

Create a custom Angular 7 HttpInterceptor

Before creating a custom Angular 7 HttpInterceptor, create a folder with the name client/src/app/interceptors. Next, create a file for the custom Angular 7 HttpInterceptor with the name client/src/app/interceptors/token.interceptor.ts. Open and edit that file the add these imports.

import { Injectable } from ‘@angular/core’;
import {
    HttpRequest,
    HttpHandler,
    HttpEvent,
    HttpInterceptor,
    HttpResponse,
    HttpErrorResponse
} from ‘@angular/common/http’;
import { Observable, throwError } from ‘rxjs’;
import { map, catchError } from ‘rxjs/operators’;
import { Router } from ‘@angular/router’;

Create a class that implementing HttpInterceptor method.

@Injectable()
export class TokenInterceptor implements HttpInterceptor {

}

Inject the required module to the constructor inside the class.

constructor(private router: Router) {}

Implement a custom Interceptor function.

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    const token = localStorage.getItem(‘token’);
    if (token) {
      request = request.clone({
        setHeaders: {
          ‘Authorization’: 'Bearer ’ + token
        }
      });
    }
    if (!request.headers.has(‘Content-Type’)) {
      request = request.clone({
        setHeaders: {
          ‘content-type’: ‘application/json’
        }
      });
    }
    request = request.clone({
      headers: request.headers.set(‘Accept’, ‘application/json’)
    });
    return next.handle(request).pipe(
      map((event: HttpEvent<any>) => {
        if (event instanceof HttpResponse) {
          console.log(‘event—>>>’, event);
        }
        return event;
      }),
      catchError((error: HttpErrorResponse) => {
        console.log(error);
        if (error.status === 401) {
          this.router.navigate([‘login’]);
        }
        if (error.status === 400) {
          alert(error.error);
        }
        return throwError(error);
      }));
}

Next, we have to register this custom HttpInterceptor and HttpClientModule. Open and edit client/src/app.module.ts then add these imports.

import { HTTP_INTERCEPTORS, HttpClientModule } from ‘@angular/common/http’;
import { TokenInterceptor } from ‘./interceptors/token.interceptor’;

Add HttpClientModule to the @NgModule imports array.

imports: [
  BrowserModule,
  AppRoutingModule,
  HttpClientModule
],

Add the Interceptor modules to the provider array of the @NgModule.

providers: [
  {
    provide: HTTP_INTERCEPTORS,
    useClass: TokenInterceptor,
    multi: true
  }
],

Now, the HTTP interceptor is ready to intercept any request to the API.

Create Services for Accessing Book and Authentication API

To accessing the ASP Net Core Web API from Angular 7 application, we have to create services for that. Type these commands to generate the Angular 7 services from the client folder.

ng g service auth
ng g service book

Next, open and edit client/src/app/auth.service.ts then add these imports.

import { HttpClient } from ‘@angular/common/http’;
import { Observable, of } from ‘rxjs’;
import { catchError, tap } from ‘rxjs/operators’;

Declare a variable as ASP Net Core Web API URL.

apiUrl = ‘http://192.168.0.5:5000/api/auth/’;

Inject the HttpClient module inside the constructor.

constructor(private http: HttpClient) { }

Create all required functions for Login, Logout, Register, and helper functions.

login(data: any): Observable<any> {
  return this.http.post<any>(this.apiUrl + ‘login’, data)
    .pipe(
      tap(_ => this.log(‘login’)),
      catchError(this.handleError(‘login’, []))
    );
}

register(data: any): Observable<any> {
  return this.http.post<any>(this.apiUrl + ‘register’, data)
    .pipe(
      tap(_ => this.log(‘login’)),
      catchError(this.handleError(‘login’, []))
    );
}

private handleError<T>(operation = ‘operation’, result?: T) {
  return (error: any): Observable<T> => {

    // TODO: send the error to remote logging infrastructure
    console.error(error); // log to console instead

    // TODO: better job of transforming error for user consumption
    this.log(${operation} failed: ${error.message});

    // Let the app keep running by returning an empty result.
    return of(result as T);
  };
}

/** Log a HeroService message with the MessageService */
private log(message: string) {
  console.log(message);
}

Next, create an object class that represents Book data client/src/app/book/book.ts then replace all file contents with these.

export class Book {
    bookId: number;
    isbn: string;
    title: string;
    author: string;
    description: string;
    publisher: string;
    publishedYear: number;
    price: number;
}

Next, open and edit client/src/app/services/book.service.ts then replace all codes with this.

import { Injectable } from ‘@angular/core’;
import { Book } from ‘./book/book’;
import { HttpClient } from ‘@angular/common/http’;
import { Observable, of } from ‘rxjs’;
import { catchError, tap } from ‘rxjs/operators’;

@Injectable({
  providedIn: ‘root’
})
export class BookService {

  apiUrl = ‘http://192.168.0.5:5000/api/book’;

  constructor(private http: HttpClient) { }

  getBooks(): Observable<Book[]> {
    return this.http.get<Book[]>(this.apiUrl + ‘book’)
      .pipe(
        tap(_ => this.log(‘fetched books’)),
        catchError(this.handleError(‘getBooks’, []))
      );
  }

  private handleError<T>(operation = ‘operation’, result?: T) {
    return (error: any): Observable<T> => {

      // TODO: send the error to remote logging infrastructure
      console.error(error); // log to console instead

      // TODO: better job of transforming error for user consumption
      this.log(${operation} failed: ${error.message});

      // Let the app keep running by returning an empty result.
      return of(result as T);
    };
  }

  /** Log a HeroService message with the MessageService */
  private log(message: string) {
    console.log(message);
  }
}


Display List of Book using Angular 7 Material

To display a list of books to the Angular 7 template. First, open and edit client/src/app/book/book.component.ts then add these imports.

import { Book } from ‘./book’;
import { BookService } from ‘…/book.service’;
import { AuthService } from ‘…/auth.service’;
import { Router } from ‘@angular/router’;

Next, inject the Book and Auth Services to the constructor.

constructor(private bookService: BookService, private authService: AuthService, private router: Router) { }

Declare these variables before the constructor.

data: Book[] = [];
displayedColumns: string[] = [‘bookId’, ‘isbn’, ‘title’];
isLoadingResults = true;

Create a function for consuming or get a book list from the booking service.

getBooks(): void {
  this.bookService.getBooks()
    .subscribe(books => {
      this.data = books;
      console.log(this.data);
      this.isLoadingResults = false;
    }, err => {
      console.log(err);
      this.isLoadingResults = false;
    });
}

Call this function from ngOnInit.

ngOnInit() {
  this.getBooks();
}

Add a function for log out the current session.

logout() {
  localStorage.removeItem(‘token’);
  this.router.navigate([‘login’]);
}

Next, for the user interface (UI) we will use Angular 7 Material and CDK. There’s a CLI for generating a Material component like Table as a component, but we will create or add the Table component from scratch to existing component. Type this command to install Angular 7 Material.

ng add @angular/material

If there are some questions, answer them like below.

? Choose a prebuilt theme name, or “custom” for a custom theme: Purple/Green       [ Preview: https://material.angular.i
o?theme=purple-green ]
? Set up HammerJS for gesture recognition? Yes
? Set up browser animations for Angular Material? Yes

Next, we have to register all required Angular Material components or modules to src/app/app.module.ts. Open and edit that file then add this imports.

import {
  MatInputModule,
  MatPaginatorModule,
  MatProgressSpinnerModule,
  MatSortModule,
  MatTableModule,
  MatIconModule,
  MatButtonModule,
  MatCardModule,
  MatFormFieldModule } from ‘@angular/material’;

Also, modify FormsModule import to add ReactiveFormsModule.

import { FormsModule, ReactiveFormsModule } from ‘@angular/forms’;

Register the above modules to @NgModule imports.

imports: [
  BrowserModule,
  FormsModule,
  HttpClientModule,
  AppRoutingModule,
  ReactiveFormsModule,
  BrowserAnimationsModule,
  MatInputModule,
  MatTableModule,
  MatPaginatorModule,
  MatSortModule,
  MatProgressSpinnerModule,
  MatIconModule,
  MatButtonModule,
  MatCardModule,
  MatFormFieldModule
],

Next, open and edit client/src/app/book/book.component.html then replace all HTML tags with this Angular 7 Material tags.

<div class=“example-container mat-elevation-z8”>
  <div class=“example-loading-shade”
       *ngIf=“isLoadingResults”>
    <mat-spinner *ngIf=“isLoadingResults”></mat-spinner>
  </div>
  <div class=“button-row”>
    <a mat-flat-button color=“primary” (click)=“logout()”>Logout</a>
  </div>
  <div class=“mat-elevation-z8”>
    <table mat-table [dataSource]=“data” class=“example-table”>

      <!-- Book ID Column -->
      <ng-container matColumnDef=“bookId”>
        <th mat-header-cell *matHeaderCellDef>Book ID</th>
        <td mat-cell *matCellDef=“let row”>{{row.bookId}}</td>
      </ng-container>

      <!-- ISBN Column -->
      <ng-container matColumnDef=“isbn”>
        <th mat-header-cell *matHeaderCellDef>ISBN</th>
        <td mat-cell *matCellDef=“let row”>{{row.isbn}}</td>
      </ng-container>

      <!-- Title Column -->
      <ng-container matColumnDef=“title”>
        <th mat-header-cell *matHeaderCellDef>Title</th>
        <td mat-cell *matCellDef=“let row”>{{row.title}}</td>
      </ng-container>

      <tr mat-header-row *matHeaderRowDef=“displayedColumns”></tr>
      <tr mat-row matRowDef=“let row; columns: displayedColumns;”></tr>
    </table>
  </div>
</div>

Finally, we have to align the style for this page. Open and edit client/src/app/book/book.component.scss then replace all SCSS codes with these.

/ Structure */
.example-container {
    position: relative;
    padding: 5px;
}

.example-table-container {
    position: relative;
    max-height: 400px;
    overflow: auto;
}

table {
    width: 100%;
}

.example-loading-shade {
    position: absolute;
    top: 0;
    left: 0;
    bottom: 56px;
    right: 0;
    background: rgba(0, 0, 0, 0.15);
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
}

.example-rate-limit-reached {
    color: #980000;
    max-width: 360px;
    text-align: center;
}

/* Column Widths */
.mat-column-number,
.mat-column-state {
    max-width: 64px;
}

.mat-column-created {
    max-width: 124px;
}

.mat-flat-button {
    margin: 5px;
}


Create a Login and Register Page

This time for authentication part. Open and edit client/src/app/auth/login/login.component.ts then add these imports.

import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from ‘@angular/forms’;
import { AuthService } from ‘…/…/auth.service’;
import { Router } from ‘@angular/router’;
import { ErrorStateMatcher } from ‘@angular/material/core’;

Declare these variables before the constructor.

loginForm: FormGroup;
email = ‘’;
password = ‘’;
matcher = new MyErrorStateMatcher();
isLoadingResults = false;

Inject the imported modules to the constructor.

constructor(private formBuilder: FormBuilder, private router: Router, private authService: AuthService) { }

Initialize NgForm to the NgOnInit function.

ngOnInit() {
  this.loginForm = this.formBuilder.group({
    ‘email’ : [null, Validators.required],
    ‘password’ : [null, Validators.required]
  });
}

Add a function to submit the login form.

onFormSubmit(form: NgForm) {
  this.authService.login(form)
    .subscribe(res => {
      console.log(res);
      if (res.token) {
        localStorage.setItem(‘token’, res.token);
        this.router.navigate([‘book’]);
      }
    }, (err) => {
      console.log(err);
    });
}

Add a function to go to the Register page.

register() {
  this.router.navigate([‘register’]);
}

Add a class that handles the form validation below this class.

/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const isSubmitted = form && form.submitted;
    return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
  }
}

Next, open and edit client/src/app/auth/login/login.component.html then replace all HTML tags with these.

<div class=“example-container mat-elevation-z8”>
  <div class=“example-loading-shade”
       *ngIf=“isLoadingResults”>
    <mat-spinner *ngIf=“isLoadingResults”></mat-spinner>
  </div>
  <mat-card class=“example-card”>
    <form [formGroup]=“loginForm” (ngSubmit)=“onFormSubmit(loginForm.value)”>
      <mat-form-field class=“example-full-width”>
        <input matInput type=“email” placeholder=“Email” formControlName=“email”
               [errorStateMatcher]=“matcher”>
        <mat-error>
          <span *ngIf=“!loginForm.get(‘email’).valid && loginForm.get(‘email’).touched”>Please enter your email</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field class=“example-full-width”>
        <input matInput type=“password” placeholder=“Password” formControlName=“password”
               [errorStateMatcher]=“matcher”>
        <mat-error>
          <span ngIf=“!loginForm.get(‘password’).valid && loginForm.get(‘password’).touched”>Please enter your password</span>
        </mat-error>
      </mat-form-field>
      <div class=“button-row”>
        <button type=“submit” [disabled]=“!loginForm.valid” mat-flat-button color=“primary”>Login</button>
      </div>
      <div class=“button-row”>
        <button type=“button” mat-flat-button color=“primary” (click)=“register()”>Register</button>
      </div>
    </form>
  </mat-card>
</div>

Next, give this page a style by open and edit client/src/app/auth/login/login.component.scss then applies these styles codes.

/ Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}

.example-full-width {
  width: 100%;
}

.example-full-width:nth-last-child() {
  margin-bottom: 10px;
}

.button-row {
  margin: 10px 0;
}

.mat-flat-button {
  margin: 5px;
}

Next, for register page, open and edit client/src/app/auth/register/register.component.ts then replace all Typescript codes with these.

import { Component, OnInit } from ‘@angular/core’;
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from ‘@angular/forms’;
import { AuthService } from ‘…/…/auth.service’;
import { Router } from ‘@angular/router’;
import { ErrorStateMatcher } from ‘@angular/material/core’;

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

  registerForm: FormGroup;
  fullName = ‘’;
  email = ‘’;
  password = ‘’;
  isLoadingResults = false;
  matcher = new MyErrorStateMatcher();

  constructor(private formBuilder: FormBuilder, private router: Router, private authService: AuthService) { }

  ngOnInit() {
    this.registerForm = this.formBuilder.group({
      ‘fullName’ : [null, Validators.required],
      ‘email’ : [null, Validators.required],
      ‘password’ : [null, Validators.required]
    });
  }

  onFormSubmit(form: NgForm) {
    this.authService.register(form)
      .subscribe(res => {
        this.router.navigate([‘login’]);
      }, (err) => {
        console.log(err);
        alert(err.error);
      });
  }

}

/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const isSubmitted = form && form.submitted;
    return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
  }
}


Next, open and edit client/src/app/auth/register/register.component.html then replace all HTML tags with these.

<div class=“example-container mat-elevation-z8”>
  <div class=“example-loading-shade”
       *ngIf=“isLoadingResults”>
    <mat-spinner *ngIf=“isLoadingResults”></mat-spinner>
  </div>
  <mat-card class=“example-card”>
    <form [formGroup]=“registerForm” (ngSubmit)=“onFormSubmit(registerForm.value)”>
      <mat-form-field class=“example-full-width”>
        <input matInput type=“fullName” placeholder=“Full Name” formControlName=“fullName”
                [errorStateMatcher]=“matcher”>
        <mat-error>
          <span *ngIf=“!registerForm.get(‘fullName’).valid && registerForm.get(‘fullName’).touched”>Please enter your Full Name</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field class=“example-full-width”>
        <input matInput type=“email” placeholder=“Email” formControlName=“email”
               [errorStateMatcher]=“matcher”>
        <mat-error>
          <span *ngIf=“!registerForm.get(‘email’).valid && registerForm.get(‘email’).touched”>Please enter your email</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field class=“example-full-width”>
        <input matInput type=“password” placeholder=“Password” formControlName=“password”
               [errorStateMatcher]=“matcher”>
        <mat-error>
          <span ngIf=“!registerForm.get(‘password’).valid && registerForm.get(‘password’).touched”>Please enter your password</span>
        </mat-error>
      </mat-form-field>
      <div class=“button-row”>
        <button type=“submit” [disabled]=“!registerForm.valid” mat-flat-button color=“primary”>Register</button>
      </div>
    </form>
  </mat-card>
</div>

Finally, open and edit client/src/app/auth/register/register.component.scss then replace all SCSS codes with these.

/ Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}

.example-full-width {
  width: 100%;
}

.example-full-width:nth-last-child() {
  margin-bottom: 10px;
}

.button-row {
  margin: 10px 0;
}

.mat-flat-button {
  margin: 5px;
}


Run and Test the ASP Net Core, SQL Server, and Angular 7: Web App Authentication

Let’s test the whole ASP Net Core and Angular 7 Web application. First, we have to run the ASP Net Core Web API from the Visual Studio Code Terminal.

dotnet watch run

Next, run this command from the CMD to run Angular 7 application.

cd client
ng serve

If you get this error in the Visual Studio Code terminal while running Angular 7 in the browser.

Failed to authenticate HTTPS connection.
System.Security.Authentication.AuthenticationException: Authentication failed, see inner exception. —> System.ComponentModel.Win32Exception: An unknown error occurred while processing the certificate
   — End of inner exception stack trace —
   at System.Net.Security.SslState.StartSendAuthResetSignal(ProtocolToken message, AsyncProtocolRequest asyncRequest, ExceptionDispatchInfo exception)
   at System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.PartialFrameCallback(AsyncProtocolRequest asyncRequest)
— End of stack trace from previous location where exception was thrown —
   at System.Net.Security.SslState.ThrowIfExceptional()
   at System.Net.Security.SslState.InternalEndProcessAuthentication(LazyAsyncResult lazyResult)
   at System.Net.Security.SslState.EndProcessAuthentication(IAsyncResult result)
   at System.Net.Security.SslStream.EndAuthenticateAsServer(IAsyncResult asyncResult)
   at System.Net.Security.SslStream.<>c.<AuthenticateAsServerAsync>b__51_1(IAsyncResult iar)
   at System.Threading.Tasks.TaskFactory1.FromAsyncCoreLogic(IAsyncResult iar, Func2 endFunction, Action1 endAction, Task1 promise, Boolean requiresSynchronization)
— End of stack trace from previous location where exception was thrown —
   at Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionAdapter.InnerOnConnectionAsync(ConnectionAdapterContext context)

Just, open and edit Properties/launchSettings.json then replace the applicationUrl in AspNetAngularAuth object with this.

“applicationUrl”: “http://192.168.0.5:5000;http://localhost:5000”,

Then re-run again ASP Net Core Web API. And here they are, the Angular 7 authentication app looks like.

That it’s, the ASP Net Core, SQL Server, and Angular 7: Web App Authentication. You can find the full source code in our GitHub.

That just the basic. If you need more deep learning about ASP.NET Core, Angular or related you can take the following cheap course:


Angular 8 (formerly Angular 2) - The Complete Guide

Angular Crash Course for Busy Developers

Originally published by Didin J at djamware.com

==========================================

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter

#asp-net #sql-server #angular #web-development

ASP Net Core, SQL Server, and Angular 7: Web App Authentication
3 Likes157.90 GEEK