{"id":80774,"date":"2023-02-02T08:00:37","date_gmt":"2023-02-02T07:00:37","guid":{"rendered":"https:\/\/code-maze.com\/?p=80774"},"modified":"2024-01-31T15:25:07","modified_gmt":"2024-01-31T14:25:07","slug":"dotnet-angular-two-factor-authentication-with-using-google-authenticator","status":"publish","type":"post","link":"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/","title":{"rendered":"Two Factor Authentication with Web API and Angular using Google Authenticator"},"content":{"rendered":"<p>In this article, we will enable Two-Factor Authentication with Web API and Angular using Google Authenticator. <strong>We can use the Google Authenticator app to get a One-Time Password (OTP)<\/strong> value to enter during login.<\/p>\n<p>The initial steps of this article have been taken from the <a href=\"https:\/\/code-maze.com\/angular-security-with-asp-net-core-identity\/\" target=\"_blank\" rel=\"noopener\">Angular Security with ASP.NET Core Identity<\/a> series. You may refer to this series for more details and explanations.<\/p>\n<p>We will start with creating an ASP.NET Core Web API project that will host our backend. Inside this project, we will also create the Angular frontend project. Moreover, you can check on the <a href=\"https:\/\/code-maze.com\/angular-series\/\" target=\"_blank\" rel=\"noopener\">Angular<\/a> series for a complete account of how to set up an Angular project with ASP.NET Core.<\/p>\n<div style=\"padding: 20px; border-left: 5px #dc2323 solid; display: block; margin-bottom: 20px; box-shadow: 1px 1px 5px 0px lightgrey;\">To download the source code for this article, you can visit our <a href=\"https:\/\/github.com\/CodeMazeBlog\/CodeMazeGuides\/tree\/main\/authorization-dotnet\/TFAGoogleAuthenticatorAngular\" target=\"_blank\" rel=\"nofollow noopener\">GitHub repository<\/a>.<\/div>\n<p>Let&#8217;s start.<\/p>\n<h2>Preparing the Environment<\/h2>\n<p>To begin with, let&#8217;s install the ASP.NET Core Identity library. We will find it in NuGet under the <code>Microsoft.AspNetCore.Identity.EntityFramework<\/code> name.<\/p>\n<p>Then, we create a new folder named <code>Models<\/code>. Inside this folder, let&#8217;s create a new <code>User<\/code> class that inherits from the <code>IdentityUser<\/code> class:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\">public class User : IdentityUser\r\n{\r\n    public string? FirstName { get; set; }\r\n    public string? LastName { get; set; }\r\n}<\/pre>\n<p>Let&#8217;s proceed with adding a context class:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\">public class RepositoryContext : IdentityDbContext&lt;User&gt;\r\n{\r\n    public RepositoryContext(DbContextOptions options)\r\n    : base(options)\r\n    {\r\n    }\r\n\r\n    protected override void OnModelCreating(ModelBuilder modelBuilder)\r\n    {\r\n        base.OnModelCreating(modelBuilder);\r\n    }\r\n}<\/pre>\n<p>Next, let&#8217;s modify the <code>Program.cs<\/code> file to register ASP.NET Core Identity in our project:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\">var builder = WebApplication.CreateBuilder(args);\r\nvar allowSpecificOrigins = \"two_factor_auth_cors\";\r\n\r\nbuilder.Services.AddCors(options =&gt;\r\n{\r\n    options.AddPolicy(allowSpecificOrigins,\r\n        builder =&gt;\r\n          builder.WithOrigins(\"http:\/\/localhost:4200\", \"https:\/\/localhost:4200\")\r\n        .AllowAnyMethod()\r\n        .AllowAnyHeader()\r\n        .AllowCredentials());\r\n});\r\n\r\nbuilder.Services.AddDbContext&lt;RepositoryContext&gt;(opts =&gt;\r\n    opts.UseSqlServer(builder.Configuration.GetConnectionString(\"sqlConnection\")));\r\n\r\nbuilder.Services.AddIdentity&lt;User, IdentityRole&gt;()\r\n    .AddEntityFrameworkStores&lt;RepositoryContext&gt;()\r\n    .AddDefaultTokenProviders();\r\n\r\nbuilder.Services.AddAutoMapper(typeof(MappingProfile));\r\nbuilder.Services.AddControllers();\r\n\r\nvar app = builder.Build();\r\napp.UseCors(allowSpecificOrigins);\r\napp.UseHttpsRedirection();\r\napp.UseAuthorization();\r\napp.MapControllers();\r\napp.Run();<\/pre>\n<p><code>Program.cs<\/code> is also the place to <strong>register the Context and the AutoMapper<\/strong>, as well as add <strong>CORS support<\/strong>.<\/p>\n<p>Finally, let&#8217;s <strong>create a migration for all ASP.NET Core Identity tables<\/strong> in our database:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\">PM&gt; Add-Migration IdentityTablesCreation \r\nPM&gt; Update-Database<\/pre>\n<h2>Two-Factor Authentication Options<\/h2>\n<p>Upon successful registration, the user will move to the login page. The user will initially log in without Two-Factor Authentication. Then, after logging in, the user can go to the settings page to set up the Two-Factor Authentication with Google Authenticator.<\/p>\n<p>Of course, there are different approaches how to handle TFA. One way could be to make TFA mandatory for logging in. This way, we would require the user to set up TFA at the same time as registration.<\/p>\n<p>In this article, <strong>we choose to make TFA optional. The user should go to the TFA setup page and manually enable it.<\/strong><\/p>\n<p>Here, we will not discuss the registration process as it is not related to the Two-Factor Authentication process. However, you may check the article&#8217;s code on GitHub, especially the <code>AccountsController<\/code> <a href=\"https:\/\/github.com\/CodeMazeBlog\/CodeMazeGuides\/tree\/main\/authorization-dotnet\/TFAGoogleAuthenticatorAngular\/TFAGoogleAuthenticatorAngular\/Controllers\/AccountsController.cs\" target=\"_blank\" rel=\"noopener\">file<\/a> in ASP.NET Core and the <code>RegistrationComponent<\/code> <a href=\"https:\/\/github.com\/CodeMazeBlog\/CodeMazeGuides\/blob\/main\/authorization-dotnet\/TFAGoogleAuthenticatorAngular\/TFAGoogleAuthenticatorAngularFrontend\/src\/app\/components\/register-user\/register-user.component.ts\" target=\"_blank\" rel=\"nofollow noopener\">file<\/a> in Angular for the implementation. Moreover, you can have a look at our article on <a href=\"https:\/\/code-maze.com\/user-registration-angular-aspnet-identity\/\" target=\"_blank\" rel=\"noopener\">User registration<\/a> for a detailed account.<\/p>\n<p>Here is a screenshot of the registration page:<\/p>\n<p><a href=\"https:\/\/code-maze.com\/wp-content\/uploads\/2023\/01\/register-1.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium\" src=\"https:\/\/code-maze.com\/wp-content\/uploads\/2023\/01\/register-1.png\" width=\"718\" height=\"596\" \/><\/a><\/p>\n<p>Now, let&#8217;s start with the Two-Factor Authentication setup.<\/p>\n<h2>Two-Factor Authentication Using Google Authenticator in Angular Frontend<\/h2>\n<p>First, let&#8217;s add a <code>HomeComponent<\/code> that is going to be a simple home page containing a link to the settings page:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"html\">&lt;h1&gt;Two factor authentication with Google Authenticator&lt;\/h1&gt;\r\n&lt;p&gt;\r\n    &lt;a routerLink=\"\/tfa-setup\"&gt;Press here to set up two-factor authentication&lt;\/a&gt;\r\n&lt;\/p&gt;\r\n<\/pre>\n<p>Then, let&#8217;s add the routing entry for this component in the <code>app-routing.module.ts<\/code> file:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"typescript\">const routes: Routes = [\r\n  { path: '', component: HomeComponent  },\r\n  { path: 'register', component: RegisterUserComponent  }\r\n}<\/pre>\n<p>To set up Two-Factor Authentication (TFA) with Google Authenticator, we create a <code>TfaSetupComponent<\/code> component.<\/p>\n<p>The TFA setup process consists of<strong> two steps<\/strong>:<\/p>\n<ul>\n<li>The TFA setup page displays a <strong>QR code that the user must scan using the Google Authenticator app.<\/strong> Alternatively, the user may <strong>type the displayed authenticator code into the app<\/strong>.<\/li>\n<li>The user <strong>enters the code provided by Google Authenticator<\/strong> in the corresponding text box.<\/li>\n<\/ul>\n<p>The user can always <strong>disable TFA<\/strong> by pressing the respective button on the page.<\/p>\n<p>We will use the <code>&lt;qr-code&gt;<\/code> Angular component to display the <strong>QR Code<\/strong>. Let&#8217;s install it via NPM:<\/p>\n<p><code>npm install angular2-qrcode<\/code><\/p>\n<p>We should not forget to import it in the <code>app.module.ts<\/code> file:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"typescript\">imports: [\r\n  ...\r\n  QRCodeModule,\r\n  ...\r\n]<\/pre>\n<p>The template of the <code>TfaSetupComponent<\/code> consists of two blocks. The first part handles Two-Factor Authentication deactivation:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"html\">&lt;h1&gt;Two-Factor Authentication with Google Authenticator - Setup&lt;\/h1&gt;\r\n\r\n&lt;div *ngIf=\"isLoading\" class=\"spinner-border\" role=\"status\"&gt;&lt;\/div&gt;\r\n\r\n&lt;div *ngIf=\"showError\"  class=\"alert alert-danger\" role=\"alert\"&gt;\r\n    {{errorMessage}}\r\n&lt;\/div&gt;\r\n\r\n&lt;div *ngIf=\"!isLoading\"&gt;\r\n    &lt;div *ngIf =\"tfaEnabled\"&gt;\r\n        &lt;p class=\"alert alert-primary\"&gt;Two-factor authentication has been enabled&lt;\/p&gt;\r\n        &lt;div class=\"box\"&gt;\r\n            &lt;h2&gt;Current settings&lt;\/h2&gt;\r\n            &lt;p&gt;QR code:&lt;\/p&gt;\r\n            &lt;qr-code [value]=\"qrInfo\"&gt;&lt;\/qr-code&gt;\r\n            &lt;p&gt;Secret key:&lt;\/p&gt;\r\n            &lt;pre&gt;{{authenticatorKey}}&lt;\/pre&gt;\r\n            \r\n            &lt;button class=\"btn btn-info\" (click)=\"disableTfa()\"&gt;Disable Two-Factor Authentication&lt;\/button&gt;\r\n        &lt;\/div&gt;\r\n    &lt;\/div&gt;\r\n\r\n\r\n<\/pre>\n<p>The second part handles Two-Factor Authentication activation:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"html\">    &lt;div *ngIf =\"!tfaEnabled\"&gt;\r\n        &lt;p class=\"alert alert-danger\"&gt;Two-factor authentication has not been enabled&lt;\/p&gt;\r\n        &lt;div&gt;\r\n            &lt;div class=\"box\"&gt;\r\n                &lt;h2&gt;Step 1&lt;\/h2&gt;\r\n                &lt;p&gt;Using the Google Authenticator app, you should scan the following QR code:&lt;\/p&gt;\r\n                &lt;qr-code [value]=\"qrInfo\"&gt;&lt;\/qr-code&gt;\r\n                &lt;p&gt;Alternatively, you may enter the following code to the Google Authenticator app:&lt;\/p&gt;\r\n                &lt;pre&gt;{{authenticatorKey}}&lt;\/pre&gt;   \r\n            &lt;\/div&gt;\r\n            \r\n            &lt;div class=\"box\"&gt;\r\n                &lt;h2&gt;Step 2&lt;\/h2&gt;\r\n                &lt;div&gt;\r\n                    &lt;form [formGroup]=\"tfaForm\" (ngSubmit)=\"enableTfa(tfaForm.value)\"&gt;\r\n                        &lt;div class=\"mb-3 row\"&gt;\r\n                            &lt;label for=\"code\" class=\"col-form-label col-sm-2\"&gt;\r\n                              Enter here the code provided by the Google Authenticator app:\r\n                            &lt;\/label&gt;\r\n                            &lt;div class=\"col-md-5\"&gt;\r\n                                &lt;input type=\"text\" id=\"code\" formControlName=\"code\" class=\"form-control\" \/&gt;\r\n                            &lt;\/div&gt;\r\n                            &lt;div class=\"col-md-5\"&gt;\r\n                                &lt;em *ngIf=\"validateControl('code') &amp;&amp; hasError('code', 'required')\"&gt;\r\n                                  Code is required\r\n                                &lt;\/em&gt;\r\n                            &lt;\/div&gt;\r\n                        &lt;\/div&gt;\r\n                        &lt;div class=\"mb-3 row\"&gt;\r\n                            &lt;div class=\"col-md-3\"&gt;\r\n                                &lt;button type=\"submit\" class=\"btn btn-info\" [disabled]=\"!tfaForm.valid\"&gt;\r\n                                  Enable Two-Factor Authentication\r\n                                &lt;\/button&gt;\r\n                            &lt;\/div&gt;\r\n                        &lt;\/div&gt;\r\n                    &lt;\/form&gt;\r\n                &lt;\/div&gt;    \r\n            &lt;\/div&gt;\r\n        &lt;\/div&gt;        \r\n    &lt;\/div&gt;\r\n    &lt;a href=\"\/\" class=\"btn btn-info\" &gt;Return to Home Page&lt;\/a&gt;\r\n&lt;\/div&gt;<\/pre>\n<p>You can check the full component template <a href=\"https:\/\/github.com\/CodeMazeBlog\/CodeMazeGuides\/blob\/main\/authorization-dotnet\/TFAGoogleAuthenticatorAngular\/TFAGoogleAuthenticatorAngularFrontend\/src\/app\/components\/tfa-setup\/tfa-setup.component.html\" target=\"_blank\" rel=\"nofollow noopener\">here<\/a>.<\/p>\n<p>Now, let&#8217;s implement the component&#8217;s class. During initialization, we retrieve the authenticator key from the backend API:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"typescript\">import { HttpErrorResponse } from '@angular\/common\/http';\r\nimport { Component, OnInit } from '@angular\/core';\r\nimport { FormControl, FormGroup, Validators } from '@angular\/forms';\r\nimport { TfaSetupDto } from 'src\/app\/models\/TfaSetupDto';\r\nimport { AuthenticationService } from 'src\/app\/shared\/services\/authentication.service';\r\n\r\n@Component({\r\n  selector: 'app-tfa-setup',\r\n  templateUrl: '.\/tfa-setup.component.html',\r\n  styleUrls: ['.\/tfa-setup.component.css']\r\n})\r\nexport class TfaSetupComponent implements OnInit {\r\n\r\n  tfaForm: FormGroup  = new FormGroup({\r\n    code: new FormControl(\"\", [Validators.required])\r\n  });\r\n\r\n  isLoading: boolean = true;\r\n  tfaEnabled: boolean = false;\r\n  showError: boolean = false;\r\n  errorMessage: string = \"\";\r\n  qrInfo: string = \"\";\r\n  authenticatorKey: string = \"\";\r\n\r\n  constructor(public authService: AuthenticationService) { }\r\n\r\n  ngOnInit(): void {\r\n    let email = localStorage.getItem(\"email\") ?? '';\r\n    this.authService.getTfaSetup(email)\r\n      .subscribe((response:TfaSetupDto) =&gt; {\r\n        this.tfaEnabled = response.isTfaEnabled ?? false;\r\n        this.qrInfo = response.formattedKey ?? '';\r\n        this.authenticatorKey = response.authenticatorKey ?? '';\r\n        this.isLoading = false;\r\n      }\r\n    );\r\n  }\r\n\r\n  validateControl = (controlName: string) =&gt; {\r\n    return this.tfaForm.get(controlName)?.invalid &amp;&amp; this.tfaForm.get(controlName)?.touched\r\n  }\r\n\r\n  hasError = (controlName: string, errorName: string) =&gt; {\r\n    return this.tfaForm.get(controlName)?.hasError(errorName)\r\n  }\r\n<\/pre>\n<p>When the user chooses to disable TFA, we call the <code>disableTFA()<\/code> function:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"typescript\">disableTfa = () =&gt; {\r\n  let email = localStorage.getItem(\"email\") ?? '';\r\n  this.authService.disableTfa(email)\r\n  .subscribe({\r\n    next: (res:any) =&gt; {\r\n      this.tfaEnabled = false;\r\n    },\r\n    error: (err: HttpErrorResponse) =&gt; {\r\n      this.showError = true;\r\n      this.errorMessage \r\n        = \"Two-factor authentication was not disabled for this account (Message: \" + err.message + \")\";\r\n    }})\r\n}<\/pre>\n<p>Also, we use the <code>enableTFA()<\/code> function to enable Two-Factor Authentication:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"typescript\">  enableTfa = (tfaFormValue: any) =&gt; {\r\n    const tfaForm = {... tfaFormValue };\r\n\r\n    const tfaSetupDto: TfaSetupDto = {\r\n      email: localStorage.getItem(\"email\") ?? '',\r\n      code: tfaForm.code\r\n    }\r\n\r\n    this.authService.postTfaSetup(tfaSetupDto)\r\n    .subscribe({\r\n      next: (res:any) =&gt; {\r\n        this.tfaEnabled = true;\r\n      },\r\n      error: (err: HttpErrorResponse) =&gt; {\r\n        this.showError = true;\r\n        this.errorMessage \r\n          = \"Two-factor authentication was not activated for this account (Message: \" + err.message + \")\";\r\n      }})\r\n  }  \r\n}<\/pre>\n<p>The key will be available in two forms:<\/p>\n<ul>\n<li><strong>Formatted<\/strong>, for the creation of the QR code<\/li>\n<li><strong>Unformatted<\/strong>, so that the user may enter it manually if needed<\/li>\n<\/ul>\n<p>We will see how we can create both versions when we will develop the backend functionality later on.<\/p>\n<p>Next, let&#8217;s add more functions to the <code>AuthenticationService<\/code> class:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"typescript\">public getTfaSetup = (email: string) =&gt; {\r\n  return this.http.get&lt;TfaSetupDto&gt; (`${environment.apiUrl}\/accounts\/tfa-setup?email=${email}`);\r\n}\r\n\r\npublic postTfaSetup = (body: TfaSetupDto) =&gt; {\r\n  return this.http.post&lt;TfaSetupDto&gt; (`${environment.apiUrl}\/accounts\/tfa-setup`, body);\r\n}\r\n\r\npublic disableTfa = (email: string) =&gt; {\r\n  return this.http.delete&lt;TfaSetupDto&gt; (`${environment.apiUrl}\/accounts\/tfa-setup?email=${email}`);\r\n}\r\n<\/pre>\n<p>As shown above, our component calls <code>postTfaSetup()<\/code> and <code>disableTfa()<\/code> functions from the authentication service when the user chooses to enable or disable TFA support respectively.<\/p>\n<p>Now we need to implement the functionality for TFA set up in the backend.<\/p>\n<h2>Two-Factor Authentication using Google Authenticator in ASP.NET Core Backend<\/h2>\n<p>Let&#8217;s start with creating the authenticator key with the <code>UserManager<\/code> object:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\">[HttpGet(\"tfa-setup\")]\r\npublic async Task&lt;IActionResult&gt; GetTfaSetup(string email)\r\n{\r\n    var user = await _userManager.FindByNameAsync(email);\r\n\r\n    if (user == null)\r\n        return BadRequest(\"User does not exist\");\r\n\r\n    var isTfaEnabled = await _userManager.GetTwoFactorEnabledAsync(user);\r\n\r\n    var authenticatorKey = await _userManager.GetAuthenticatorKeyAsync(user);\r\n    if (authenticatorKey == null)\r\n    {\r\n        await _userManager.ResetAuthenticatorKeyAsync(user);\r\n        authenticatorKey = await _userManager.GetAuthenticatorKeyAsync(user);\r\n    }\r\n    var formattedKey = GenerateQrCode(email, authenticatorKey);\r\n\r\n    return Ok(new TfaSetupDto \r\n        { IsTfaEnabled = isTfaEnabled, AuthenticatorKey = authenticatorKey, FormattedKey = formattedKey });\r\n}<\/pre>\n<p>Then, let&#8217;s add the <code>GenerateQRCode()<\/code> method to create the formatted key that the frontend will use to display the QR Code:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\">private string GenerateQRCode(string email, string unformattedKey)\r\n{\r\n    return string.Format(\r\n        AuthenticatorUriFormat,\r\n        _urlEncoder.Encode(\"Code Maze Two-Factor Auth\"),\r\n        _urlEncoder.Encode(email),\r\n        unformattedKey);\r\n}<\/pre>\n<p>In order for Google Authenticator to successfully scan the QR Code, let&#8217;s format its URI in a specific way:<\/p>\n<p><code class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\">private const string AuthenticatorUriFormat = \"otpauth:\/\/totp\/{0}:{1}?secret={2}&amp;issuer={0}&amp;digits=6\";<\/code><\/p>\n<p>Next, let&#8217;s define the <code>TfaSetupDto<\/code> class that will transmit the formatted and unformatted keys:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\">public class TfaSetupDto\r\n{\r\n    public string Email { get; set; }\r\n    public string Code { get; set; }\r\n    public bool IsTfaEnabled { get; set; }\r\n    public string? AuthenticatorKey { get; set; }\r\n    public string? FormattedKey { get; set; }\r\n}<\/pre>\n<p>After the user has scanned the QR code and has entered the code provided by the Google Authenticator app, the frontend will post this code to the backend:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\">[HttpPost(\"tfa-setup\")]\r\npublic async Task&lt;IActionResult&gt; PostTfaSetup([FromBody] TfaSetupDto tfaModel)\r\n{\r\n    var user = await _userManager.FindByNameAsync(tfaModel.Email);\r\n\r\n    var isValidCode = await _userManager\r\n        .VerifyTwoFactorTokenAsync(user, \r\n          _userManager.Options.Tokens.AuthenticatorTokenProvider, \r\n          tfaModel.Code);\r\n\r\n    if (isValidCode)\r\n    {\r\n        await _userManager.SetTwoFactorEnabledAsync(user, true);\r\n        return Ok(new TfaSetupDto { IsTfaEnabled = true} );\r\n    }\r\n    else\r\n    {\r\n        return BadRequest(\"Invalid code\");\r\n    }\r\n}\r\n<\/pre>\n<p>Here, let&#8217;s verify that the code is correct, again with the use of the <code>UserManager<\/code> object. If verification is successful, <code>UserManager<\/code> will enable Two Factor Authentication for the specific user.<\/p>\n<p>In cases where the user chooses to disable TFA, <code>UserManager<\/code> performs this action too:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\">[HttpDelete(\"tfa-setup\")]\r\npublic async Task&lt;IActionResult&gt; DeleteTfaSetup(string email)\r\n{\r\n    var user = await _userManager.FindByNameAsync(email);\r\n\r\n    if (user == null)\r\n    {\r\n        return BadRequest(\"User does not exist\");\r\n    }\r\n    else\r\n    {\r\n        await _userManager.SetTwoFactorEnabledAsync(user, false);\r\n        return Ok(new TfaSetupDto { IsTfaEnabled = false } );\r\n    }\r\n}<\/pre>\n<p>Now, when the user uses the TFA setup page for the first time he is prompted to scan the QR Code and then add the Google Authenticator code:<\/p>\n<p><a href=\"https:\/\/code-maze.com\/wp-content\/uploads\/2023\/01\/tfa-setup1-2.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium\" src=\"https:\/\/code-maze.com\/wp-content\/uploads\/2023\/01\/tfa-setup1-2.png\" width=\"675\" height=\"891\" \/><\/a><\/p>\n<p>When the Two-Factor Authentication is enabled, we review the current settings and we can disable TFA if we want:<\/p>\n<p><a href=\"https:\/\/code-maze.com\/wp-content\/uploads\/2023\/01\/tfa-setup2-2.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium\" src=\"https:\/\/code-maze.com\/wp-content\/uploads\/2023\/01\/tfa-setup2-2.png\" width=\"418\" height=\"677\" \/><\/a><\/p>\n<h2>Login Using Google Authenticator in ASP.NET Core Backend<\/h2>\n<p>Now it is time to proceed with the login process using Two-Factor Authentication with Web API and Angular using Google Authenticator. The login process consists of two steps. First, the user uses a plain login dialog to enter his email and password as usual.<\/p>\n<p>If the user provides the correct credentials and enables Two-Factor Authentication, our app asks the user to enter the <strong>One-Time Password (OTP)<\/strong> <strong>from Google Authenticator<\/strong>. If the user has chosen to disable Two-Factor Authentication, he is directly logged into the app and redirected to the home page.<\/p>\n<p>Let&#8217;s begin by creating Data Transfer Objects (DTOs) that will be used during the login process. First, let&#8217;s create the <code>UserForAuthenticationDto<\/code> class to transmit the email and password from the client:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\">public class UserForAuthenticationDto\r\n{\r\n    [Required(ErrorMessage = \"Email is required.\")]\r\n    public string Email { get; set; }\r\n    [Required(ErrorMessage = \"Password is required.\")]\r\n    public string Password { get; set; }\r\n}<\/pre>\n<p>Moreover, class <code>AuthResponseDto<\/code> will transfer the corresponding response:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\">public class AuthResponseDto \r\n{ \r\n    public bool IsAuthSuccessful { get; set; } \r\n    public bool IsTfaEnabled { get; set; } \r\n    public string? ErrorMessage { get; set; } \r\n    public string? Token { get; set; } \r\n}<\/pre>\n<p>Let&#8217;s proceed with the modification of the <code>AccountsController<\/code> class by adding two methods, <code>Login()<\/code> and <code>LoginTfa()<\/code>:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\">[HttpPost(\"login\")]\r\npublic async Task&lt;IActionResult&gt; Login([FromBody] UserForAuthenticationDto userForAuthentication)\r\n{\r\n    var user = await _userManager.FindByNameAsync(userForAuthentication.Email);\r\n\r\n    if (user == null \r\n        || !await _userManager.CheckPasswordAsync(user, userForAuthentication.Password))\r\n        return Unauthorized(new AuthResponseDto { ErrorMessage = \"Invalid Authentication\" });\r\n\r\n    var isTfaEnabled = await _userManager.GetTwoFactorEnabledAsync(user);\r\n\r\n    if(!isTfaEnabled)\r\n    {\r\n        var signingCredentials = _jwtHandler.GetSigningCredentials();\r\n        var claims = _jwtHandler.GetClaims(user);\r\n        var tokenOptions = _jwtHandler.GenerateTokenOptions(signingCredentials, claims);\r\n        var token = new JwtSecurityTokenHandler().WriteToken(tokenOptions);\r\n\r\n        return Ok(new AuthResponseDto { IsAuthSuccessful = true, IsTfaEnabled = false, Token = token });\r\n    }\r\n\r\n    return Ok(new AuthResponseDto { IsAuthSuccessful = true, IsTfaEnabled = true });\r\n}<\/pre>\n<p>The <code>Login()<\/code> method implements the first step of the login process, as it verifies the correctness of the credentials and checks whether Two-Function authentication has been enabled.<\/p>\n<p>Let&#8217;s add the <code>LoginTfa()<\/code> method:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\">[HttpPost(\"login-tfa\")]\r\npublic async Task&lt;IActionResult&gt; LoginTfa([FromBody] TfaDto tfaDto)\r\n{\r\n    var user = await _userManager.FindByNameAsync(tfaDto.Email);\r\n\r\n    if (user == null)\r\n        return Unauthorized(new AuthResponseDto { ErrorMessage = \"Invalid Authentication\" });\r\n\r\n    var validVerification = \r\n      await _userManager.VerifyTwoFactorTokenAsync(\r\n         user, _userManager.Options.Tokens.AuthenticatorTokenProvider, tfaDto.Code);\r\n    if (!validVerification)\r\n        return BadRequest(\"Invalid Token Verification\");\r\n\r\n    var signingCredentials = _jwtHandler.GetSigningCredentials();\r\n    var claims = _jwtHandler.GetClaims(user);\r\n    var tokenOptions = _jwtHandler.GenerateTokenOptions(signingCredentials, claims);\r\n    var token = new JwtSecurityTokenHandler().WriteToken(tokenOptions);\r\n\r\n    return Ok(new AuthResponseDto { IsAuthSuccessful = true, IsTfaEnabled = true, Token = token });\r\n}<\/pre>\n<p>The <code>LoginTfa()<\/code> method is used during the second step and verifies the correctness of the OTP submitted by the user. Upon successful login, we create a new <strong>JWT token<\/strong> and send it to the frontend.<\/p>\n<p>You may check the code to see how we use JWT in our project. Moreover, we already have an article on <a href=\"https:\/\/code-maze.com\/angular-authentication-aspnet-identity\/\" target=\"_blank\" rel=\"noopener\">JWT support for Angular<\/a>.<\/p>\n<h2>Login Using Google Authenticator in Angular Frontend<\/h2>\n<p>On the frontend side, let&#8217;s also begin by creating DTOs for communication with the backend:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"typescript\">export interface UserForAuthenticationDto {\r\n    email?: string;\r\n    password?: string;\r\n}<\/pre>\n<p>Interface <code>UserForAuthenticationDto<\/code>\u00a0carries the user credentials, while <code>AuthResponseDto<\/code> returns the response:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"typescript\">export interface AuthResponseDto {\r\n    isAuthSuccessful: boolean;\r\n    isTfaEnabled: boolean;\r\n    errorMessage: string;\r\n    token: string;\r\n}<\/pre>\n<p>Next, let&#8217;s create the login component:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"html\">&lt;div class=\"card\"&gt;\r\n    &lt;div class=\"card-body\"&gt;\r\n        &lt;h1 class=\"card-title\"&gt;Login&lt;\/h1&gt;\r\n  \r\n        &lt;div *ngIf=\"showError\" class=\"alert alert-danger\" role=\"alert\"&gt;\r\n            {{errorMessage}}\r\n        &lt;\/div&gt;\r\n        \r\n        &lt;div&gt;\r\n            &lt;form [formGroup]=\"loginForm\" autocomplete=\"off\" novalidate \r\n                (ngSubmit)=\"loginUser(loginForm.value)\"&gt;\r\n                &lt;div class=\"mb-3 row\"&gt;\r\n                    &lt;label for=\"email\" class=\"col-form-label col-sm-2\"&gt;Email:&lt;\/label&gt;\r\n                    &lt;div class=\"col-md-5\"&gt;\r\n                        &lt;input type=\"text\" id=\"email\" formControlName=\"email\" class=\"form-control\" \/&gt;\r\n                    &lt;\/div&gt;\r\n                    &lt;div class=\"col-md-5\"&gt;\r\n                        &lt;em *ngIf=\"validateControl('email') &amp;&amp; hasError('email', 'required')\"&gt;\r\n                            Email is required\r\n                        &lt;\/em&gt;\r\n                    &lt;\/div&gt;\r\n                &lt;\/div&gt;\r\n                &lt;div class=\"mb-3 row\"&gt;\r\n                    &lt;label for=\"password\" class=\"col-form-label col-sm-2\"&gt;Password:&lt;\/label&gt;\r\n                    &lt;div class=\"col-md-5\"&gt;\r\n                        &lt;input type=\"password\" id=\"password\" formControlName=\"password\" class=\"form-control\" \/&gt;\r\n                    &lt;\/div&gt;\r\n                    &lt;div class=\"col-md-5\"&gt;\r\n                        &lt;em *ngIf=\"validateControl('password') &amp;&amp; hasError('password', 'required')\"&gt;\r\n                            Password is required\r\n                        &lt;\/em&gt;\r\n                    &lt;\/div&gt;\r\n                &lt;\/div&gt;\r\n                &lt;br&gt;\r\n                &lt;div class=\"mb-3 row\"&gt;\r\n                    &lt;div class=\"col-md-1\"&gt;\r\n                        &lt;button type=\"submit\" class=\"btn btn-info\" [disabled]=\"!loginForm.valid\"&gt;\r\n                            Login\r\n                        &lt;\/button&gt;\r\n                    &lt;\/div&gt;\r\n                &lt;\/div&gt;\r\n            &lt;\/form&gt;        \r\n        &lt;\/div&gt;\r\n    &lt;\/div&gt;\r\n  &lt;\/div&gt;<\/pre>\n<p>Let&#8217;s add the component class. First, the initialization part:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"typescript\">import { HttpErrorResponse } from '@angular\/common\/http';\r\nimport { Router, ActivatedRoute } from '@angular\/router';\r\nimport { AuthenticationService } from '.\/..\/..\/shared\/services\/authentication.service';\r\nimport { Component, OnInit } from '@angular\/core';\r\nimport { FormGroup, FormControl, Validators } from '@angular\/forms';\r\nimport { UserForAuthenticationDto } from 'src\/app\/models\/UserForAuthenticationDto';\r\nimport { AuthResponseDto } from 'src\/app\/models\/AuthResponseDto';\r\n\r\n@Component({\r\n  selector: 'app-login',\r\n  templateUrl: '.\/login.component.html',\r\n  styleUrls: ['.\/login.component.css']\r\n})\r\nexport class LoginComponent implements OnInit {\r\n  private returnUrl: string = \"\";\r\n  isTfaEnabled: boolean = false;\r\n  \r\n  loginForm: FormGroup  = new FormGroup({\r\n    email: new FormControl(\"\", [Validators.required]),\r\n    password: new FormControl(\"\", [Validators.required])\r\n  });\r\n\r\n  tfaForm: FormGroup  = new FormGroup({\r\n    tfaCode: new FormControl(\"\", [Validators.required])\r\n  });\r\n\r\n  errorMessage: string = '';\r\n  showError: boolean = false;\r\n  email:string = \"\";\r\n\r\n  constructor(\r\n    private authService: AuthenticationService, \r\n    private router: Router, \r\n    private route: ActivatedRoute) { }\r\n  \r\n  ngOnInit(): void {\r\n    this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '\/';\r\n  }\r\n\r\n  validateControl = (controlName: string) =&gt; {\r\n    return this.loginForm.get(controlName)?.invalid \r\n        &amp;&amp; this.loginForm.get(controlName)?.touched\r\n  }\r\n\r\n  hasError = (controlName: string, errorName: string) =&gt; {\r\n    return this.loginForm.get(controlName)?.hasError(errorName)\r\n  }\r\n<\/pre>\n<p>Then, the <code>loginUser()<\/code> function:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"typescript\">  loginUser = (loginFormValue: any) =&gt; {\r\n    this.showError = false;\r\n    const login = {... loginFormValue };\r\n\r\n    const userForAuth: UserForAuthenticationDto = {\r\n      email: login.email,\r\n      password: login.password\r\n    }\r\n\r\n    this.authService.loginUser(userForAuth)\r\n    .subscribe({\r\n      next: (res:AuthResponseDto) =&gt; {\r\n       this.isTfaEnabled = res.isTfaEnabled;\r\n\r\n       if(this.isTfaEnabled){\r\n        this.router.navigate(['twostepverification'], \r\n          { queryParams: { returnUrl: this.returnUrl, email: login.email }})\r\n       }\r\n       else{\r\n        localStorage.setItem(\"token\", res.token);\r\n        localStorage.setItem(\"email\", login.email);\r\n        this.router.navigate([this.returnUrl]);\r\n       }\r\n    },\r\n    error: (err: HttpErrorResponse) =&gt; {\r\n      this.errorMessage = err.message;\r\n      this.showError = true;\r\n    }})\r\n  }\r\n}<\/pre>\n<p>You can find the component class file <a href=\"https:\/\/github.com\/CodeMazeBlog\/CodeMazeGuides\/blob\/main\/authorization-dotnet\/TFAGoogleAuthenticatorAngular\/TFAGoogleAuthenticatorAngularFrontend\/src\/app\/components\/login\/login.component.ts\" target=\"_blank\" rel=\"nofollow noopener\">here<\/a>.<\/p>\n<p>This is the login page that we get:<\/p>\n<p><a href=\"https:\/\/code-maze.com\/wp-content\/uploads\/2023\/01\/login-1.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium\" src=\"https:\/\/code-maze.com\/wp-content\/uploads\/2023\/01\/login-1.png\" width=\"702\" height=\"420\" \/><\/a><\/p>\n<p>Upon successful login, we check the response to see whether Two-Factor Authentication is enabled. In this case, we forward the user to the component for OTP entry (<code>TwoStepVerificationComponent<\/code>). If not, we store the returned JSON Web Token (JWT) in the local storage and redirect the user to the home page.<\/p>\n<p>The second step of the login process takes place in the <code>TwoStepVerificationComponent<\/code>:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"html\">&lt;div class=\"card\"&gt;\r\n    &lt;div class=\"card-body\"&gt;\r\n        &lt;h1 class=\"card-title\"&gt;Two Step Verification&lt;\/h1&gt;\r\n        &lt;div *ngIf=\"showError\" class=\"alert alert-danger\" role=\"alert\"&gt;\r\n            {{errorMessage}}\r\n        &lt;\/div&gt;\r\n        &lt;form [formGroup]=\"twoStepForm\" autocomplete=\"off\" novalidate \r\n            (ngSubmit)=\"loginUser(twoStepForm.value)\"&gt;\r\n            &lt;div class=\"mb-3 row\"&gt;\r\n                &lt;label for=\"twoFactorCode\" class=\"col-form-label col-sm-2\"&gt;Code:&lt;\/label&gt;\r\n                &lt;div class=\"col-md-5\"&gt;\r\n                    &lt;input type=\"text\" id=\"twoFactorCode\" \r\n                        formControlName=\"twoFactorCode\" class=\"form-control\" \/&gt;\r\n                &lt;\/div&gt;\r\n                &lt;div class=\"col-md-5\"&gt;\r\n                    &lt;em *ngIf=\"validateControl('twoFactorCode') &amp;&amp; hasError('twoFactorCode', 'required')\"&gt;\r\n                        The Code is required\r\n                    &lt;\/em&gt;\r\n                &lt;\/div&gt;\r\n            &lt;\/div&gt;\r\n            &lt;br&gt;\r\n            &lt;div class=\"mb-3 row\"&gt;\r\n                &lt;div class=\"col-md-1\"&gt;\r\n                    &lt;button type=\"submit\" class=\"btn btn-info\" [disabled]=\"!twoStepForm.valid\"&gt;\r\n                        Submit\r\n                    &lt;\/button&gt;\r\n                &lt;\/div&gt;\r\n            &lt;\/div&gt;\r\n        &lt;\/form&gt;\r\n    &lt;\/div&gt;\r\n&lt;\/div&gt;<\/pre>\n<p>First, let&#8217;s add the initialization part of the component:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"typescript\">import { HttpErrorResponse } from '@angular\/common\/http';\r\nimport { Component, OnInit } from '@angular\/core';\r\nimport { FormControl, FormGroup, Validators } from '@angular\/forms';\r\nimport { ActivatedRoute, Router } from '@angular\/router';\r\nimport { AuthResponseDto } from 'src\/app\/models\/AuthResponseDto';\r\nimport { TfaDto } from 'src\/app\/models\/tfaDto';\r\nimport { AuthenticationService } from 'src\/app\/shared\/services\/authentication.service';\r\n\r\n@Component({\r\n  selector: 'app-two-step-verification',\r\n  templateUrl: '.\/two-step-verification.component.html',\r\n  styleUrls: ['.\/two-step-verification.component.css']\r\n})\r\nexport class TwoStepVerificationComponent implements OnInit {\r\n  private email: string = \"\";\r\n  private returnUrl: string = \"\";\r\n\r\n  twoStepForm = new FormGroup({\r\n    twoFactorCode: new FormControl('', [Validators.required]),\r\n  });\r\n  showError: boolean = false;\r\n  errorMessage: string = \"\";\r\n\r\n  constructor(private authService: AuthenticationService,\r\n    private route: ActivatedRoute, \r\n    private router: Router) { }\r\n\r\n  ngOnInit(): void {\r\n      this.email = this.route.snapshot.queryParams['email'];\r\n      this.returnUrl = this.route.snapshot.queryParams['returnUrl'];\r\n      this.email = this.route.snapshot.queryParams['email'];\r\n  }\r\n\r\n  validateControl = (controlName: string) =&gt; {\r\n    return this.twoStepForm.get(controlName)?.invalid &amp;&amp; this.twoStepForm.get(controlName)?.touched\r\n  }\r\n  hasError = (controlName: string, errorName: string) =&gt; {\r\n    return this.twoStepForm.get(controlName)?.hasError(errorName)\r\n  }\r\n<\/pre>\n<p>Then, the <code>loginUser()<\/code> function:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"typescript\">  loginUser = (twoStepFromValue: any) =&gt; {\r\n    this.showError = false;\r\n    \r\n    const formValue = { ...twoStepFromValue };\r\n    let twoFactorDto: TfaDto = {\r\n      email: this.email,\r\n      code: formValue.twoFactorCode\r\n    }\r\n    this.authService.loginUserTfa(twoFactorDto)\r\n    .subscribe({\r\n      next: (res:AuthResponseDto) =&gt; {\r\n        localStorage.setItem(\"token\", res.token);\r\n        localStorage.setItem(\"email\", this.email);\r\n        this.router.navigate([this.returnUrl]);\r\n      },\r\n      error: (err: HttpErrorResponse) =&gt; {\r\n        this.errorMessage = err.message;\r\n        this.showError = true;\r\n      }\r\n    })\r\n  }  \r\n}<\/pre>\n<p>Here, <strong>the component sends the OTP to the backend API for verification<\/strong>. Upon successful request, it saves the JWT and moves to the home page. You can find the component class file <a href=\"https:\/\/github.com\/CodeMazeBlog\/CodeMazeGuides\/blob\/main\/authorization-dotnet\/TFAGoogleAuthenticatorAngular\/TFAGoogleAuthenticatorAngularFrontend\/src\/app\/components\/two-step-verification\/two-step-verification.component.ts\" target=\"_blank\" rel=\"nofollow noopener\">here<\/a>.<\/p>\n<p>Here is a snapshot of this page:<\/p>\n<p><a href=\"https:\/\/code-maze.com\/wp-content\/uploads\/2023\/01\/login2-2.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium\" src=\"https:\/\/code-maze.com\/wp-content\/uploads\/2023\/01\/login2-2.png\" width=\"605\" height=\"369\" \/><\/a><\/p>\n<p>Also, let&#8217;s not forget to modify the <code>AuthenticationService<\/code> by adding the functions for the login steps:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"typescript\">public loginUser = (body: UserForAuthenticationDto) =&gt; {\r\n  return this.http.post&lt;AuthResponseDto&gt;(`${environment.apiUrl}\/accounts\/login`, body);\r\n}\r\n\r\npublic loginUserTfa = (body: TfaDto) =&gt; {\r\n  return this.http.post&lt;AuthResponseDto&gt;(`${environment.apiUrl}\/accounts\/login-tfa`, body);\r\n}<\/pre>\n<p>Finally, let&#8217;s add routes to the new components in <code>app-routing.module.ts<\/code>:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"typescript\">const routes: Routes = [\r\n  { path: '', component: HomeComponent, canActivate: [AuthGuard]  },\r\n  { path: 'register', component: RegisterUserComponent  },\r\n  { path: 'login', component: LoginComponent },\r\n  { path: 'twostepverification', component: TwoStepVerificationComponent },\r\n  { path: 'tfa-setup', component: TfaSetupComponent, canActivate: [AuthGuard]  }\r\n];<\/pre>\n<p>One final addition is the <code>AuthGuard<\/code> class that enables access to protected routes:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"typescript\">import { Injectable } from \"@angular\/core\";\r\nimport { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from \"@angular\/router\";\r\n\r\n@Injectable({\r\n  providedIn: 'root'\r\n})\r\nexport class AuthGuard implements CanActivate {\r\n    constructor(\r\n      private router: Router\r\n    ) { }\r\n  \r\n    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {\r\n      if(localStorage.getItem(\"email\")){\r\n        return true;\r\n      }\r\n      this.router.navigate(['\/login'], { queryParams: { returnUrl: state.url } });\r\n      return false;             \r\n    }\r\n}\r\n  <\/pre>\n<p>That&#8217;s it! Now we can use Google Authenticator to provide One-Time Passwords for our web application.<\/p>\n<h2>Conclusion<\/h2>\n<p>In this article, we learned how to implement <strong>Two-Factor Authentication with Web API and Angular using Google Authenticator<\/strong> in our web applications. Now, we can secure our applications by adding a second step, with the generation of a <strong>One-Time Password (OTP)<\/strong> from Google Authenticator.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this article, we will enable Two-Factor Authentication with Web API and Angular using Google Authenticator. We can use the Google Authenticator app to get a One-Time Password (OTP) value to enter during login. The initial steps of this article have been taken from the Angular Security with ASP.NET Core Identity series. You may refer [&hellip;]<\/p>\n","protected":false},"author":6,"featured_media":62193,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_et_pb_use_builder":"","_et_pb_old_content":"","_et_gb_content_width":"","footnotes":""},"categories":[168,1806,2079],"tags":[23,629,1613,739,1614,1401],"class_list":["post-80774","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-angular","category-security","category-web-api","tag-angular","tag-asp-net-core-identity","tag-google-authenticator","tag-login","tag-register","tag-two-factor-authentication","et-has-post-format-content","et_post_format-et-post-format-standard"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v24.7 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Two Factor Authentication with Web API and Angular using Google Authenticator - Code Maze<\/title>\n<meta name=\"description\" content=\"In this article, we will enable Two-Factor Authentication with Web API and Angular using Google Authenticator.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Two Factor Authentication with Web API and Angular using Google Authenticator - Code Maze\" \/>\n<meta property=\"og:description\" content=\"In this article, we will enable Two-Factor Authentication with Web API and Angular using Google Authenticator.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/\" \/>\n<meta property=\"og:site_name\" content=\"Code Maze\" \/>\n<meta property=\"article:published_time\" content=\"2023-02-02T07:00:37+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-01-31T14:25:07+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/code-maze.com\/wp-content\/uploads\/2021\/12\/social-dotnet-security.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1100\" \/>\n\t<meta property=\"og:image:height\" content=\"620\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Code Maze\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@https:\/\/twitter.com\/CodeMazeBlog\" \/>\n<meta name=\"twitter:site\" content=\"@CodeMazeBlog\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Code Maze\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"15 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":[\"Article\",\"BlogPosting\"],\"@id\":\"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/\"},\"author\":{\"name\":\"Code Maze\",\"@id\":\"https:\/\/code-maze.com\/#\/schema\/person\/09d29b223012c8e94a68ba62861d0b04\"},\"headline\":\"Two Factor Authentication with Web API and Angular using Google Authenticator\",\"datePublished\":\"2023-02-02T07:00:37+00:00\",\"dateModified\":\"2024-01-31T14:25:07+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/\"},\"wordCount\":1450,\"commentCount\":4,\"publisher\":{\"@id\":\"https:\/\/code-maze.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/code-maze.com\/wp-content\/uploads\/2021\/12\/social-dotnet-security.png\",\"keywords\":[\"Angular\",\"Asp.NET Core Identity\",\"google authenticator\",\"Login\",\"register\",\"two-factor authentication\"],\"articleSection\":[\"Angular\",\"Security\",\"Web API\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/\",\"url\":\"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/\",\"name\":\"Two Factor Authentication with Web API and Angular using Google Authenticator - Code Maze\",\"isPartOf\":{\"@id\":\"https:\/\/code-maze.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/code-maze.com\/wp-content\/uploads\/2021\/12\/social-dotnet-security.png\",\"datePublished\":\"2023-02-02T07:00:37+00:00\",\"dateModified\":\"2024-01-31T14:25:07+00:00\",\"description\":\"In this article, we will enable Two-Factor Authentication with Web API and Angular using Google Authenticator.\",\"breadcrumb\":{\"@id\":\"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/#primaryimage\",\"url\":\"https:\/\/code-maze.com\/wp-content\/uploads\/2021\/12\/social-dotnet-security.png\",\"contentUrl\":\"https:\/\/code-maze.com\/wp-content\/uploads\/2021\/12\/social-dotnet-security.png\",\"width\":1100,\"height\":620,\"caption\":\".NET Security\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/code-maze.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Two Factor Authentication with Web API and Angular using Google Authenticator\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/code-maze.com\/#website\",\"url\":\"https:\/\/code-maze.com\/\",\"name\":\"Code Maze\",\"description\":\"Learn. Code. Succeed.\",\"publisher\":{\"@id\":\"https:\/\/code-maze.com\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/code-maze.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/code-maze.com\/#organization\",\"name\":\"Code Maze\",\"url\":\"https:\/\/code-maze.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/code-maze.com\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/code-maze.com\/wp-content\/uploads\/2020\/01\/Code-Maze-Only-Logo-Transparent-HRez.png\",\"contentUrl\":\"https:\/\/code-maze.com\/wp-content\/uploads\/2020\/01\/Code-Maze-Only-Logo-Transparent-HRez.png\",\"width\":3511,\"height\":3510,\"caption\":\"Code Maze\"},\"image\":{\"@id\":\"https:\/\/code-maze.com\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/x.com\/CodeMazeBlog\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/code-maze.com\/#\/schema\/person\/09d29b223012c8e94a68ba62861d0b04\",\"name\":\"Code Maze\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/code-maze.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/code-maze.com\/wp-content\/uploads\/2020\/01\/Code-Maze-Only-Logo-Transparent-HRez-150x150.png\",\"contentUrl\":\"https:\/\/code-maze.com\/wp-content\/uploads\/2020\/01\/Code-Maze-Only-Logo-Transparent-HRez-150x150.png\",\"caption\":\"Code Maze\"},\"description\":\"This is the standard author on the site. Most articles are published by individual authors, with their profiles, but when several authors have contributed, we publish collectively as a part of this profile.\",\"sameAs\":[\"https:\/\/www.linkedin.com\/company\/codemaze\/\",\"https:\/\/x.com\/https:\/\/twitter.com\/CodeMazeBlog\"],\"url\":\"https:\/\/code-maze.com\/author\/codemazecontributor\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Two Factor Authentication with Web API and Angular using Google Authenticator - Code Maze","description":"In this article, we will enable Two-Factor Authentication with Web API and Angular using Google Authenticator.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/","og_locale":"en_US","og_type":"article","og_title":"Two Factor Authentication with Web API and Angular using Google Authenticator - Code Maze","og_description":"In this article, we will enable Two-Factor Authentication with Web API and Angular using Google Authenticator.","og_url":"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/","og_site_name":"Code Maze","article_published_time":"2023-02-02T07:00:37+00:00","article_modified_time":"2024-01-31T14:25:07+00:00","og_image":[{"width":1100,"height":620,"url":"https:\/\/code-maze.com\/wp-content\/uploads\/2021\/12\/social-dotnet-security.png","type":"image\/png"}],"author":"Code Maze","twitter_card":"summary_large_image","twitter_creator":"@https:\/\/twitter.com\/CodeMazeBlog","twitter_site":"@CodeMazeBlog","twitter_misc":{"Written by":"Code Maze","Est. reading time":"15 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":["Article","BlogPosting"],"@id":"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/#article","isPartOf":{"@id":"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/"},"author":{"name":"Code Maze","@id":"https:\/\/code-maze.com\/#\/schema\/person\/09d29b223012c8e94a68ba62861d0b04"},"headline":"Two Factor Authentication with Web API and Angular using Google Authenticator","datePublished":"2023-02-02T07:00:37+00:00","dateModified":"2024-01-31T14:25:07+00:00","mainEntityOfPage":{"@id":"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/"},"wordCount":1450,"commentCount":4,"publisher":{"@id":"https:\/\/code-maze.com\/#organization"},"image":{"@id":"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/#primaryimage"},"thumbnailUrl":"https:\/\/code-maze.com\/wp-content\/uploads\/2021\/12\/social-dotnet-security.png","keywords":["Angular","Asp.NET Core Identity","google authenticator","Login","register","two-factor authentication"],"articleSection":["Angular","Security","Web API"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/","url":"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/","name":"Two Factor Authentication with Web API and Angular using Google Authenticator - Code Maze","isPartOf":{"@id":"https:\/\/code-maze.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/#primaryimage"},"image":{"@id":"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/#primaryimage"},"thumbnailUrl":"https:\/\/code-maze.com\/wp-content\/uploads\/2021\/12\/social-dotnet-security.png","datePublished":"2023-02-02T07:00:37+00:00","dateModified":"2024-01-31T14:25:07+00:00","description":"In this article, we will enable Two-Factor Authentication with Web API and Angular using Google Authenticator.","breadcrumb":{"@id":"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/#primaryimage","url":"https:\/\/code-maze.com\/wp-content\/uploads\/2021\/12\/social-dotnet-security.png","contentUrl":"https:\/\/code-maze.com\/wp-content\/uploads\/2021\/12\/social-dotnet-security.png","width":1100,"height":620,"caption":".NET Security"},{"@type":"BreadcrumbList","@id":"https:\/\/code-maze.com\/dotnet-angular-two-factor-authentication-with-using-google-authenticator\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/code-maze.com\/"},{"@type":"ListItem","position":2,"name":"Two Factor Authentication with Web API and Angular using Google Authenticator"}]},{"@type":"WebSite","@id":"https:\/\/code-maze.com\/#website","url":"https:\/\/code-maze.com\/","name":"Code Maze","description":"Learn. Code. Succeed.","publisher":{"@id":"https:\/\/code-maze.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/code-maze.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/code-maze.com\/#organization","name":"Code Maze","url":"https:\/\/code-maze.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/code-maze.com\/#\/schema\/logo\/image\/","url":"https:\/\/code-maze.com\/wp-content\/uploads\/2020\/01\/Code-Maze-Only-Logo-Transparent-HRez.png","contentUrl":"https:\/\/code-maze.com\/wp-content\/uploads\/2020\/01\/Code-Maze-Only-Logo-Transparent-HRez.png","width":3511,"height":3510,"caption":"Code Maze"},"image":{"@id":"https:\/\/code-maze.com\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/x.com\/CodeMazeBlog"]},{"@type":"Person","@id":"https:\/\/code-maze.com\/#\/schema\/person\/09d29b223012c8e94a68ba62861d0b04","name":"Code Maze","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/code-maze.com\/#\/schema\/person\/image\/","url":"https:\/\/code-maze.com\/wp-content\/uploads\/2020\/01\/Code-Maze-Only-Logo-Transparent-HRez-150x150.png","contentUrl":"https:\/\/code-maze.com\/wp-content\/uploads\/2020\/01\/Code-Maze-Only-Logo-Transparent-HRez-150x150.png","caption":"Code Maze"},"description":"This is the standard author on the site. Most articles are published by individual authors, with their profiles, but when several authors have contributed, we publish collectively as a part of this profile.","sameAs":["https:\/\/www.linkedin.com\/company\/codemaze\/","https:\/\/x.com\/https:\/\/twitter.com\/CodeMazeBlog"],"url":"https:\/\/code-maze.com\/author\/codemazecontributor\/"}]}},"_links":{"self":[{"href":"https:\/\/code-maze.com\/wp-json\/wp\/v2\/posts\/80774","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/code-maze.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/code-maze.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/code-maze.com\/wp-json\/wp\/v2\/users\/6"}],"replies":[{"embeddable":true,"href":"https:\/\/code-maze.com\/wp-json\/wp\/v2\/comments?post=80774"}],"version-history":[{"count":7,"href":"https:\/\/code-maze.com\/wp-json\/wp\/v2\/posts\/80774\/revisions"}],"predecessor-version":[{"id":80828,"href":"https:\/\/code-maze.com\/wp-json\/wp\/v2\/posts\/80774\/revisions\/80828"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/code-maze.com\/wp-json\/wp\/v2\/media\/62193"}],"wp:attachment":[{"href":"https:\/\/code-maze.com\/wp-json\/wp\/v2\/media?parent=80774"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/code-maze.com\/wp-json\/wp\/v2\/categories?post=80774"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/code-maze.com\/wp-json\/wp\/v2\/tags?post=80774"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}