[Introduction to Blazor] Blazor beginners tried to deploy with login and chat functions-Part 3-

9 minute read

Last time

[Introduction to Blazor] Blazor beginners tried to deploy with login to chat function ~ Part 1 ~
[Introduction to Blazor] Blazor beginners tried to deploy with login and chat functions-Part 2-
It is a continuation of.

Until last time, I used SignalR on the chat page to the point where I could see the exchange of characters between browsers.
This time I would like to review the login function.

Problems with the current login screen

As I wrote briefly before, if it is left as it is, authentication will pass if you put some characters in the user ID and password.
So, let’s make it possible to log in using the user ID and password registered in advance.

How to retain login information

When I came up with a quick method, I came up with the idea of registering user information in my own DB and extracting records with the user ID and password when the login button is pressed.

However, that wouldn’t be interesting, or it would be difficult for people looking at this article to try it, and I had a vague idea that there might be a better way, so I looked it up.

IDaaS
It seems that the fashion of Imadoki is using this ʻIDaaS`.

ʻAbbreviation for IDaaS (Identity as a Service). The reading is called "ID Earth" or "Eiders". It is a service that provides ID authentication such as SSO (single sign-on)` via the cloud. Recently, the number of devices other than Web services and smartphones has increased. Each of them is becoming more complicated as it is necessary to authenticate the ID.

image.png

image.png
Source: Comparison of Firebase and Auth0 functions of authentication service iDaaS

To summarize briefly,
In the past, IDs and passwords were stored in the DB, but there are too many services, and it is hard for developers to control them, and it is hard for consumers to remember.
If you remember one of them, you can access them all! It’s a mechanism that makes that happen.

To give a concrete example
When logging in to some service (ex. Nico Nico Douga, etc.),
Can I log in with Twitter, Google, Facebook, Apple, or a user ID and password? It means that it is easier for the user to log in.
image.png

Then what kind of ʻIDaaS` is there?

Amazon Cognito

I learned that the ʻIdP (Identity Provider) called [Amazon Cognito](https://aws.amazon.com/jp/cognito/) is provided by ʻAWS.
What’s more, it’s free (!!!) up to 50,000 certifications. This is good.

However, it doesn’t seem to support authentication such as Twitter / ʻInstagram`.
image.png

Auth0
There seems to be Auth0.

–Authentication / authorization using OIDC / OAuth2 is possible
–The screen can be freely created on the Auth0 side (you can also create and have the screen yourself in the app)
–Social cooperation is possible (when the button is turned on / off, a social login button will appear on the standard screen)
–MFA (Multi-Factor Authentication) is possible
–Pipeline / HOOK function allows you to insert lambda-like logic into a specific action such as sign-up / sign-in.

Source: Auth0 Introduction

It looks like a high-performance product.
In particular, I think it’s really nice to be able to create screens and drop sample code from the dashboard.

But at least $ 15 a month … (Free for development account only)

Firebase Authentication

Finally, I will introduce Firebase Authentication.

Firebase Authentication provides a backend service, an easy-to-use SDK, and a UI library that you can use to authenticate users in your app. Firebase Authentication allows you to authenticate using passwords, phone numbers, and popular federation identity providers (Google, Facebook, Twitter).

Source: Firebase –Documentation

It seems that this is also full of functions.
I looked it up, but I didn’t really understand the charge system.
Probably, it seems that we do not provide the service by Firebase Authentication alone.

Cognito was chosen

In terms of functionality, ʻAuth0 is probably better, but this time I'd like to implement it with a wallet-friendly Cognito`.

Preparation before implementation of Cognito

First, create a user pool by following Tutorial: Creating a user pool. please.
Make a note of the PoolID generated at this time.

Next, it seems that you need to register the domain name properly.
image.png

It’s a hassle to create a user, so it’s easier to reduce the password strength to the maximum from the policy.
image.png

And this is the most important and magnificent trap.
When creating an app client, ** uncheck “Generate client secret” and ** register. Authentication does not work when I generate a secret.
image.png

You don’t have to do this, but I think it’s easier to get the explanation later if you create a user. This user is an authorized user to authenticate with Cognito.
image.png

If you can do this, you are ready to go.

Cognito implementation

There were some articles implemented on blogs and Qiita, so I can afford it! I thought it didn’t work unexpectedly. I was about to break my heart, but please be assured that I was able to implement it safely.

The following video was the most helpful.
AWS Cognito C# example

Please install the following two libraries from NuGet.

  • Amazon.AspNetCore.Identity.Cognito
  • Amazon.Extensions.CognitoAuthentication

image.png

Make ʻIndex.razor.cs` as follows. Now the authentication will pass.

C#:Index.razor.cs


using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Amazon;
using Amazon.CognitoIdentityProvider;
using Amazon.CognitoIdentityProvider.Model;
using Amazon.Extensions.CognitoAuthentication;
using Amazon.Runtime;
using LoginTest.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;

namespace LoginTest.Pages
{
    public partial class Index
    {
        #region constant
        private const string PoolID = "us-east-1_hogehoge";
        private const string ClientID = "hogehogehogehoge";
        private RegionEndpoint Region = RegionEndpoint.USEast1;
        #endregion

        #region field
        private string _loginErrorMessage;
        #endregion

        #region property
        /// <summary>
        ///Insert the NavigationManager service dependency by specifying the Inject attribute.
        /// </summary>
        [Inject]
        public NavigationManager Navigation { get; set; }
        /// <summary>
        ///Hold login information
        /// </summary>
        public LoginData LoginData { get; set; }
        #endregion

        #region constructor
        public Index()
        {
            //Index.Since it is referenced from razor, an error will occur unless an instance is created.
            LoginData = new LoginData();
        }
        #endregion

        #region method
        /// <summary>
        ///Validate processing when processing is successful
        /// </summary>
        /// <param name="context"></param>
        public async Task OnValidSubmit(EditContext context)
        {
            Console.WriteLine($"OnValidSubmit()");

            var errorMessage = await SignInUserAsync();
            if (string.IsNullOrEmpty(errorMessage))
            {
                Navigation.NavigateTo("Chat", false);
            }
            else
            {
                _loginErrorMessage = errorMessage;
            }
        }
        /// <summary>
        ///Validate processing when processing fails
        /// </summary>
        /// <param name="context"></param>
        public void OnInvalidSubmit(EditContext context)
        {
            Console.WriteLine($"OnInvalidSubmit()");
        }
        /// <summary>
        ///Create a user that can be used with Cognito
        /// </summary>
        /// <returns></returns>
        public async Task<string> SignUpUserAsync()
        {
            //Region is "USEast1" because it is the same as the prefix of the created PoolID
            using var provider = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(), Region);
            var signUpRequest = new SignUpRequest
            {
                ClientId = ClientID,
                Username = LoginData.UserID,
                Password = LoginData.Password,
                
                UserAttributes = new List<AttributeType>
                {
                    new AttributeType{Name = "email", Value="[email protected]"},
                },
            };

            try
            {
                var result = await provider.SignUpAsync(signUpRequest).ConfigureAwait(false);
                return string.Empty;
            }
            catch (Exception e)
            {
                return e.Message;
            }
        }
        /// <summary>
        ///Determine if the user is registered with Cognito
        /// </summary>
        /// <returns></returns>
        private async Task<string> SignInUserAsync()
        {
            using var provider = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(), RegionEndpoint.USEast1);

            var userPool = new CognitoUserPool(PoolID, ClientID, provider);
            var cognitoUser = new CognitoUser(LoginData.UserID, ClientID, userPool, provider);
            var authRequest = new InitiateSrpAuthRequest
            {
                Password = LoginData.Password,
            };

            try
            {
                var authResponse = await cognitoUser.StartWithSrpAuthAsync(authRequest).ConfigureAwait(false);
                var userRequest = new GetUserRequest
                {
                    AccessToken = authResponse.AuthenticationResult.AccessToken,
                };

                await provider.GetUserAsync(userRequest).ConfigureAwait(false);
                return string.Empty;
            }
            catch (Exception e)
            {
                return e.Message;
            }
        }
        #endregion
    }
}

The SignUpUserAsync method and SignInUserAsync method are added from the article that created the login screen first.

Regarding the SignUpUserAsync method, we are just adding the user to the Cognito shown in the previous step from the code. The only caveat is that if you create a user from the SignUpUserAsync method, you need to verify the user. If you do not do this, the authentication will not pass.

There’s probably a way to update the status of your account from the code, but I didn’t know at all. If you know it, please let me know.
キャプチャ.JPG

Also, the SignInUserAsync method gets the existence of the user registered in Cognito. Both methods return an error message, and if there is no problem, an empty string is returned.

Where I was addicted to Cognito

① Mysterious Region
ʻYou have to pass a mysterious parameter called Region as the second argument of the AmazonCognitoIdentityProviderClient class. I'm not sure, but it seems that you should pass the same RegionEndpoint that is prefixed with the issued PoolID`.

② Mysterious error
At the time of authentication, the following error message was returned.

「Unable to verify secret hash for client」
Unable to check client secret hash

After reading this, I thought I didn’t pass the secret hash, so I tried to pass it, but that didn’t help. When I looked it up
I get a NotAuthorizedException when signing up for AWS Cognito User Pools (JavaScript)
, as mentioned in the article, “Client” as shown in the steps above. It seems that it was necessary to uncheck “Generate Secret”.

Without this article, I would have been infinitely addicted to it.

③ Password error
At the time of authentication, “validation errors detected: Value at’password’ failed to satisfy constraint: Member must have length greater than or equal to 6; Value at’password’ failed to satisfy constraint: Member must satisfy regular expression pattern: ^ [\ S ] +. * [\ S] + $ “}” error appeared and I thought something was wrong, but I was angry that the password at the time of user registration should be 6 digits or more.

This also seems to give an error at the time of user registration according to the policy set above. It’s amazing.

After that, I will prepare an additional place to issue an error when login authentication fails.
Please let me know if there is any other good way to write it.

Index.razor


@if (!string.IsNullOrEmpty(_loginErrorMessage))
{
    <div class="form-group">
        <p style="color: red; font-weight: bold;">@_loginErrorMessage</p>
    </div>
}                                                          

Let’s run it now.

At first, the password is intentionally mistaken and an error is displayed.
Next, it was confirmed that if the user ID and password registered in Cognito are used, the authentication will pass and the chat screen can be displayed on a sunny day.
Counter.gif

However, this is still incomplete.
Where is it? Stay tuned for the next article!

Summary

It wasn’t a big deal in terms of implementation, but the documentation was too small and it was quite awkward. I haven’t tried using Cognito, so I think I have more knowledge to use.

** Reference page **

-Comparison of Firebase and Auth0 functions of authentication service iDaaS
-Amazon Cognito