

2-Step Verification with Angular and ASP.NET Core Identity
source link: https://code-maze.com/2-step-verification-with-angular-and-aspnet-identity/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

2-Step Verification with Angular and ASP.NET Core Identity
Posted by Marinko Spasojevic | Updated Date Dec 28, 2020 | 0
Want to build great APIs? Or become even better at it? Check our program Ultimate ASP.NET Core 3 Web API and learn how to create a full production-ready ASP.NET Core API using only the latest .NET technologies. Bonus materials included!
This article is strongly connected to previous articles from this series so, for complete navigation through the entire series, you can visit the Angular with ASP.NET Core Identity page.
So, let’s look at the topics for this article:
Let’s get going.
Initial Refactoring
Since we are going to need JWT for the 2-step verification process, and we don’t want to repeat our selves, we are going to extract the logic for the token creation from the Login
action. To do that, let’s open the JwtHandler
class and modify it:
public async Task<string> GenerateToken(User user) { var signingCredentials = GetSigningCredentials(); var claims = await GetClaims(user); var tokenOptions = GenerateTokenOptions(signingCredentials, claims); var token = new JwtSecurityTokenHandler().WriteToken(tokenOptions); return token; }
Also, we can modify the accessor of each method called inside this method from public
to private
.
Now, all we have to do is to remove the code lines we transferred to the JwtHandler
class and just call the GenerateToken
method inside the Login
action:
var token = await _jwtHandler.GenerateToken(user); await _userManager.ResetAccessFailedCountAsync(user);
That’s it.
Before we move on, we have to modify just one more thing.
In our AspNetUsers
table, we have to enable the TwoFactorEnabled
column:
The EmailConfirmed
column was already set to true
and we manually modified the value of the TwoFactorEnabled
column. But if you want to do it in a code, you can use the _userManager.SetTwoFactorEnabledAsync
method.
Now, we can move on.
Generating OTP for the 2-Step Verification Process
To start with the 2-step verification process, we have to modify our Login action:
[HttpPost("Login")] public async Task<IActionResult> Login([FromBody] UserForAuthenticationDto userForAuthentication) { ... if (!await _userManager.IsEmailConfirmedAsync(user)) return Unauthorized(new AuthResponseDto { ErrorMessage = "Email is not confirmed" }); if (!await _userManager.CheckPasswordAsync(user, userForAuthentication.Password)) { ... } if (await _userManager.GetTwoFactorEnabledAsync(user)) return await GenerateOTPFor2StepVerification(user); ... return Ok(new AuthResponseDto { IsAuthSuccessful = true, Token = token }); }
Here, we use the GetTwoFactorEnabledAsync
method to check whether the user has two-factor authentication enabled. If they have, we call a private method to generate OTP. Since we don’t have that method, let’s create it:
private async Task<IActionResult> GenerateOTPFor2StepVerification(User user) { var providers = await _userManager.GetValidTwoFactorProvidersAsync(user); if (!providers.Contains("Email")) { return Unauthorized(new AuthResponseDto { ErrorMessage = "Invalid 2-Step Verification Provider." }); } var token = await _userManager.GenerateTwoFactorTokenAsync(user, "Email"); var message = new Message(new string[] { user.Email }, "Authentication token", token, null); await _emailSender.SendEmailAsync(message); return Ok(new AuthResponseDto { Is2StepVerificationRequired = true, Provider = "Email" }); }
In this action, we call the GetValidTwoFactorProviderAsync
method to verify if this user has an email provider registered. If this is not the case, we return the Unauthorized
response. But if it is true, we generate the OTP with the GenerateTwoFactorTokenAsync
method and send that OTP to the user by email.
Finally, we return the successful response but with the Is2StepVerificationRequired
property set to true and the Provider
property set to Email
. Since we don’t have these properties in the AuthResponseDto
class, we have to add them:
public class AuthResponseDto { public bool IsAuthSuccessful { get; set; } public string ErrorMessage { get; set; } public string Token { get; set; } public bool Is2StepVerificationRequired { get; set; } public string Provider { get; set; } }
Nicely done.
Let’s move on to the Angular part.
Creating 2-Step Verification Angular Component
We are going to start with the AuthResponseDto
interface modification:
export interface AuthResponseDto { isAuthSuccessful: boolean; errorMessage: string; token: string; is2StepVerificationRequired: boolean; provider: string; }
After that, we are going to create the two-step-verification
component:
ng g c authentication/two-step-verification --skipTests
Then, let’s add the route to this component in the authentication.module.ts
file:
RouterModule.forChild([ { path: 'register', component: RegisterUserComponent }, { path: 'login', component: LoginComponent }, { path: 'forgotpassword', component: ForgotPasswordComponent }, { path: 'resetpassword', component: ResetPasswordComponent }, { path: 'emailconfirmation', component: EmailConfirmationComponent }, { path: 'twostepverification', component: TwoStepVerificationComponent } ])
Since we have a route to this new component, we can modify the login.component.ts
file:
public loginUser = (loginFormValue) => { ... this._authService.loginUser('api/accounts/login', userForAuth) .subscribe(res => { if(res.is2StepVerificationRequired) { this._router.navigate(['/authentication/twostepverification'], { queryParams: { returnUrl: this._returnUrl, provider: res.provider, email: userForAuth.email }}); } else { localStorage.setItem("token", res.token); this._authService.sendAuthStateChangeNotification(res.isAuthSuccessful); this._router.navigate([this._returnUrl]); } }, ... }
As you can see, if the response is successful, we check if we require 2-step verification. If we do, we just navigate the user to the appropriate form and pass three parameters as query strings.
To continue, let’s create a new twoFactor
folder under the _interfaces
folder and add the twoFactorDto
interface inside the twoFactor
folder:
export interface TwoFactorDto { email: string; provider: string; token: string; }
Then, we have to create a new function inside the authentication service file:
public twoStepLogin = (route: string, body: TwoFactorDto) => { return this._http.post<AuthResponseDto>(this.createCompleteRoute(route, this._envUrl.urlAddress), body); }
With this in place, we can start implementing the two-step-verification
component.
Two-Step-Verification Component Implementation
Let’s start with the two-step-verification.component.html
file:
<div class="card"> <div class="card-body"> <h1 class="card-title">Two Step Verification</h1> <div *ngIf="showError" class="alert alert-danger" role="alert"> {{errorMessage}} </div> <form [formGroup]="twoStepForm" autocomplete="off" novalidate (ngSubmit)="loginUser(twoStepForm.value)"> <div class="form-group row"> <label for="twoFactorCode" class="col-form-label col-sm-2">Code:</label> <div class="col-md-5"> <input type="text" id="twoFactorCode" formControlName="twoFactorCode" class="form-control" /> </div> <div class="col-md-5"> <em *ngIf="validateControl('twoFactorCode') && hasError('twoFactorCode', 'required')">The Code is required</em> </div> </div> <br> <div class="form-group row"> <div class="col-md-1"> <button type="submit" class="btn btn-info" [disabled]="!twoStepForm.valid">Submit</button> </div> </div> </form> </div> </div>
We only have one input field where the user needs to enter the OTP sent to the email address. Also, we have a standard validation part and the Submit button.
With the HTML in place, we have to modify the .ts
file:
import { TwoFactorDto } from './../../_interfaces/twoFactor/twoFactorDto.model'; import { AuthenticationService } from './../../shared/services/authentication.service'; import { Component, OnInit } from '@angular/core'; import { FormGroup, FormControl, Validators } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; @Component({ selector: 'app-two-step-verification', templateUrl: './two-step-verification.component.html', styleUrls: ['./two-step-verification.component.css'] }) export class TwoStepVerificationComponent implements OnInit { public twoStepForm: FormGroup; public showError: boolean; public errorMessage: string; private _provider: string; private _email: string; private _returnUrl: string; constructor(private _authService: AuthenticationService, private _route: ActivatedRoute, private _router: Router) { } ngOnInit(): void { this.twoStepForm = new FormGroup({ twoFactorCode: new FormControl('', [Validators.required]), }); this._provider = this._route.snapshot.queryParams['provider']; this._email = this._route.snapshot.queryParams['email']; this._returnUrl = this._route.snapshot.queryParams['returnUrl']; } public validateControl = (controlName: string) => { return this.twoStepForm.controls[controlName].invalid && this.twoStepForm.controls[controlName].touched } public hasError = (controlName: string, errorName: string) => { return this.twoStepForm.controls[controlName].hasError(errorName) } public loginUser = (twoStepFromValue) => { this.showError = false; const formValue = { ...twoStepFromValue }; let twoFactorDto: TwoFactorDto = { email: this._email, provider: this._provider, token: formValue.twoFactorCode } this._authService.twoStepLogin('api/accounts/twostepverification', twoFactorDto) .subscribe(res => { localStorage.setItem("token", res.token); this._authService.sendAuthStateChangeNotification(res.isAuthSuccessful); this._router.navigate([this._returnUrl]); }, error => { this.errorMessage = error; this.showError = true; }) } }
This logic is familiar to us. The one interesting part is in the loginUser
function. There, if we receive a successful response from the server, we do the same thing we did in the Login component.
2-Step Verification POST Action in Web API
To finish this process, we have to create an action on the API’s side. But before we do that, we require an additional DTO class:
public class TwoFactorDto { [Required] public string Email { get; set; } [Required] public string Provider { get; set; } [Required] public string Token { get; set; } }
Now, we can implement the required action:
[HttpPost("TwoStepVerification")] public async Task<IActionResult> TwoStepVerification([FromBody]TwoFactorDto twoFactorDto) { if (!ModelState.IsValid) return BadRequest(); var user = await _userManager.FindByEmailAsync(twoFactorDto.Email); if (user == null) return BadRequest("Invalid Request"); var validVerification = await _userManager.VerifyTwoFactorTokenAsync(user, twoFactorDto.Provider, twoFactorDto.Token); if (!validVerification) return BadRequest("Invalid Token Verification"); var token = await _jwtHandler.GenerateToken(user); return Ok(new AuthResponseDto { IsAuthSuccessful = true, Token = token }); }
As you can see, we accept the twoFactorDto
object from the client and inspect if the model is valid. If it isn’t we just return a bad request. After that, we try to fetch the user by the provided email. Again, if we can’t find one, we return a bad request. If previous checks pass, we use the VerifyTwoFactorTokenAsync
method to verify if the token is valid for our user and the provider. If that’s not true, we return a bad request. Finally, if the verification is valid, we create a new token and send it to the client.
Now if we try to visit the Companies page without logging in, we are going to end up on the Login screen. Once we enter our credentials, the application will navigate us to a 2-Step Verification page:
We can see all the query parameters in the URI.
Now, let’s check our email:
Let’s get back to the application and try to enter an invalid code:
We can see the error message.
But if we enter a valid code, we are going to be navigated to the Companies page.
Excellent.
The 2-step verification is working as we expect.
Conclusion
There we go. We have learned how to implement the 2-step verification actions with both Angular and ASP.NET Core Web API. Now, our application provides an additional level of security for our users.
Until the next article…
Best regards.
Want to build great APIs? Or become even better at it? Check our program Ultimate ASP.NET Core 3 Web API and learn how to create a full production-ready ASP.NET Core API using only the latest .NET technologies. Bonus materials included!
Recommend
-
14
Angular Authentication Functionality with ASP.NET Core Identity Posted by Marinko Spasojevic | Updated Date Dec 16, 2020 |
-
24
Angular Role-Based Authorization with ASP.NET Core Identity Posted by Marinko Spasojevic | Updated Date Dec 17, 2020 |
-
17
Angular Email Confirmation with ASP.NET Core Identity Posted by Marinko Spasojevic | Updated Date Dec 21, 2020 |
-
78
How to Implement Angular Password Reset Functionality with ASP.NET Core Identity Posted by Marinko Spasojevic | Updated Date Dec 23, 2020 |
-
13
User Lockout Functionality with Angular and ASP.NET Core Identity Posted by Marinko Spasojevic | Updated Date Dec 30, 2020 |
-
24
TruNarrative launch new Identity Verification and AML product, TruPortal Leading RegTech firm TruNarrative have expanded their product line-up with new Know...
-
29
Online security is a group of methods executed with the aim of ensuring a truthful online experience for users, so their security is not compromised. While online security exists since computing does, its standards c...
-
15
Mastercard is acquiring identity verification company Ekata for $850M As online identity management grows in importance, Mastercard swooped in this morning and bo...
-
10
Not FoundYou just hit a route that doesn't exist... the sadness.LoginRadius empowers businesses to deliver a delightful customer experience and win customer trust. Using the LoginRadius Identity...
-
5
Angular Handbook: Identity and State ManagementTo get the ebook complete the formEmailAngular is an application design framework and development...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK