Prior joining Microsoft I was heavily involved in architecting and building a large scale HTTP API which will be consumed by a large number of mobile application consumers on multiple platforms (iOS, Android, and Windows Phone). Securing the API and architecting the Authentication and Authorization part for the API was one of the large and challenging features which we built from scratch as we needed only to support local database account (allowing users to login using their own existing email/username and password). As well writing a proprietary code for each platform to consume the Authentication and Authorization end points, storing the tokens, and refresh them silently was a bit challenging and required skilled mobile apps developers to implement it securely on the different platforms. Don’t ask me why we didn’t use Xamarin for cross-platform development, it is a long story 🙂 During developing the back-end API I have learned that building identity management solution is not a trivial feature, and it is better to outsource it to a cloud service provider if this is a feasible option and you want your dev team to focus on building what matters; your business features!
Recently Microsoft has announced the general availability in North America data centers of a service named “Azure Active Directory B2C” which in my humble opinion will fill the gap of having a cloud identity and access management service targeted especially for mobile apps and web developers who need to build apps for consumers; consumers who want to sign in with their existing email/usernames, create new app-specific local accounts, or use their existing social accounts (Facebook, Google, LinkedIn, Amazon, Microsoft account) to sign in into the mobile/web app.
The Azure Active Directory B2C will allow backend developers to focus on the core business of their services while they outsource the identity management to Azure Active Directory B2C including (Signing-in, Signing-up, Password reset, Edit Profile, etc..). One important feature to mention here that the service can run on Azure cloud while your HTTP API is hosted on-premise, there is no need to have everything in the cloud if your use case requires hosting your services on-premise. You can read more about all the features of Azure Active Directory B2C by visiting their official page.
The Azure Active Directory B2C can integrate seamlessly with the new unified authentication library named MSAL (Microsoft Authentication Library), this library will help developers to obtain tokens from Active Directory, Azure Active Directory B2C, and MSA for accessing protected resources. The library will support different platforms covering: .NET 4.5 + (Desktop Apps and Web apps), Windows Universal Apps, Windows Store apps (Windows 8 and above), iOS (via Xamarin), Android (via Xamarin), and .Net Core. Library still in preview, it should not be used in production application yet.
So during this series of posts, I will be covering different aspects of Azure Active Directory B2C as well integrating it with MSAL (Microsoft Authentication Library) in different front-end platforms (Desktop Application and Web Application).
Azure Active Directory B2C Overview and Policies Management
The source code for this tutorial is available on GitHub.
The MVC APP has been published on Azure App Services, so feel free to try it out using the Base URL (https://aadb2cmvcapp.azurewebsites.net)
I broke down this series into multiple posts which I’ll be posting gradually, posts are:
- Azure Active Directory B2C Overview and Policies Management – (This Post)
- Secure ASP.NET Web API 2 using Azure Active Directory B2C – (Part 2)
- Integrate Azure Active Directory B2C with ASP.NET MVC Web App (Part 3)
- Secure Desktop Application using Microsoft Authentication Library (MSAL) and Azure Active Directory B2C (Part 4)
What we’ll build in this tutorial?
During this post we will build a Web API 2 HTTP API which will be responsible for managing shipping orders (i.e. Listing orders, adding new ones, etc…), the orders data will be stored in Azure Table Storage, while we will outsource all the identity management to Azure Active Directory B2C, where service users/consumers will rely on AAD B2C to signup new accounts using their app-specific email/password, then allow them to login using their app-specific accounts.
Saying this we need a front-end apps to manipulate orders and communicate with the HTTP API, We will build a different type of apps during the series of posts where some of them will use MSAL.
So the components that all the tutorials will be built from are:
- Azure Active Directory B2C tenant for identity management, it will act as our IdP (Identity Provider).
- ASP.NET Web API 2 acting as HTTP API Service and secured by the Azure Active Directory B2C tenant.
- Different front end apps which will communicate with Azure Active Directory B2C to sign-in users, obtain tokens, send them to the protected HTTP API, and retrieve results from the HTTP API and project it on the front end applications.
So let’s get our hands dirty and start building the tutorial.
Building the Back-end Resource (Web API)
Step 1: Creating the Web API Project
In this tutorial, I’m using Visual Studio 2015 and .Net framework 4.5.2, to get started create an empty solution and name it “WebApiAzureAcitveDirectoryB2C.sln”, then add new empty ASP.NET Web application named “AADB2C.Api”, the selected template for the project will be “Empty” template with no core dependencies, check the image below:
Once the project has been created, click on it’s properties and set “SSL Enabled” to “True”, copy the “SSL URL” value and right lick on project, select “Properties”, then select the “Web” tab from the left side and paste the “SSL URL” value in the “Project Url” text field and click “Save”. We need to allow https scheme locally once we debug the application. Check the image below:
Note: If this is the first time you enable SSL locally, you might get prompted to install local IIS Express Certificate, click “Yes”.
Step 2: Install the needed NuGet Packages to bootstrap the API
This project is empty so we need to install the NuGet packages needed to setup our Owin server and configure ASP.NET Web API 2 to be hosted within an Owin server, so open NuGet Package Manager Console and install the below packages:
1 2 3 |
Install-Package Microsoft.AspNet.WebApi -Version 5.2.3 Install-Package Microsoft.AspNet.WebApi.Owin -Version 5.2.3 Install-Package Microsoft.Owin.Host.SystemWeb -Version 3.0.1 |
Step 3: Add Owin “Startup” Class
We need to build the API components because we didn’t use a ready made template, this way is cleaner and you understand the need and use for each component you install in your solution, so add a new class named “Startup”. It will contain the code below, please note that the method “ConfigureAuth” is left empty intentionally as we will visit this class many times after we create our Azure Active Directory B2C tenant, what I need to do now is to build the API without anything protection then protect with our new Azure Active Directory B2C IdP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class Startup { public void Configuration(IAppBuilder app) { HttpConfiguration config = new HttpConfiguration(); // Web API routes config.MapHttpAttributeRoutes(); ConfigureOAuth(app); app.UseWebApi(config); } public void ConfigureOAuth(IAppBuilder app) { } } |
Step 4: Add support to store data on Azure Table Storage
Note: I have decided to store the fictitious data about customers orders in Azure table storage as this service will be published online and I need to demonstrate the features on how to distinguish users data based on the signed-in user, feel free to use whatever permanent storage you like to complete this tutorial, the implementation here is simple so you can replace it with a SQL Server, MySQL, or any other NoSQL store.
So let’s add the needed NuGet packages which allow us to access the Azure Table Storage in a .NET client, I recommend you to refer to the official documentation if you need to read more about Azure Table Storage.
1 2 |
Install-Package WindowsAzure.Storage Install-Package Microsoft.WindowsAzure.ConfigurationManager |
Step 5: Add Web API Controller responsible for orders management
Now we want to add a controller which is responsible for orders management (Adding orders, listing all orders which belong to a certain user) . So add new controller named “OrdersController” inside a folder named “Controllers” and paste the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
[RoutePrefix("api/Orders")] public class OrdersController : ApiController { CloudTable cloudTable = null; public OrdersController() { // Retrieve the storage account from the connection string. CloudStorageAccount storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("StorageConnectionString")); // Create the table client. CloudTableClient tableClient = storageAccount.CreateCloudTableClient(); // Retrieve a reference to the table. cloudTable = tableClient.GetTableReference("orders"); // Create the table if it doesn't exist. // Uncomment the below line if you are not sure if the table has been created already // No need to keep checking that table exixts or not. //cloudTable.CreateIfNotExists(); } [Route("")] public IHttpActionResult Get() { //This will be read from the access token claims. var userId = "TaiseerJoudeh"; TableQuery <OrderEntity> query = new TableQuery<OrderEntity>() .Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, userId)); var orderEntitis = cloudTable.ExecuteQuery(query).Select( o => new OrderModel() { OrderID = o.RowKey, ShipperName = o.ShipperName, ShipperCity = o.ShipperCity, TS = o.Timestamp }); return Ok(orderEntitis); } [Route("")] public IHttpActionResult Post (OrderModel order) { //This will be read from the access token claims. var userId = "TaiseerJoudeh"; OrderEntity orderEntity = new OrderEntity(userId); orderEntity.ShipperName = order.ShipperName; orderEntity.ShipperCity = order.ShipperCity; TableOperation insertOperation = TableOperation.Insert(orderEntity); // Execute the insert operation. cloudTable.Execute(insertOperation); order.OrderID = orderEntity.RowKey; order.TS = orderEntity.Timestamp; return Ok(order); } } #region Classes public class OrderModel { public string OrderID { get; set; } public string ShipperName { get; set; } public string ShipperCity { get; set; } public DateTimeOffset TS { get; set; } } public class OrderEntity : TableEntity { public OrderEntity(string userId) { this.PartitionKey = userId; this.RowKey = Guid.NewGuid().ToString("N"); } public OrderEntity() { } public string ShipperName { get; set; } public string ShipperCity { get; set; } } #endregion |
What we have implemented above is very straight forward, in the constructor of the controller, we have read the connection string for the Azure Table Storage from the web.config and created a cloud table instance which references the table named “Orders”. This table will hold the Orders data.
The structure of the table is you are thinking in SQL context even Azure Table Storage is NoSQL store is simple and it is represented in the class named “OrderEntity”, the “PartitionKey” will represent the “UserId”, and the “RowKey” will represent the “OrderId”. The “OrderId” will always contain an auto generated value.
Please note the following: a) You should not store the connection string for the table storage in web.config, it is better to use Azure Key Vault for a secure way to store your keys or you can set from Azure App Settings if you are going to host the Api on Azure. b) The “UserId” now is fixed, but eventually, it will read the authenticated UserId from the access token claims once we establish the IdP provider and configure our API to rely on Azure Active Directory B2C to protect it.
By taking a look at the “POST” action, you will notice that we are adding a new record to the table storage, and the “UserId” is fixed for now and we will visit this and fix it. The same applies to the “GET” action where we read the data from Azure Table Storage for a fixed user.
Now the API is ready for testing, you can issue a GET request or POST request and the data will be stored under the fixed “UserId” which is “TaiseerJoudeh”. Note that there is no Authorization header set as the API still publicly available for anyone. Below is a reference for the POST request:
POST Request:
1 2 3 4 5 6 7 8 9 10 |
POST /api/orders HTTP/1.1 Host: localhost:44339 Content-Type: application/json Cache-Control: no-cache Postman-Token: 6f1164fa-8560-98fd-6566-892517f1003e { "shipperName" :"Nike", "shipperCity": "Clinton" } |
Configuring the Azure Active Directory B2C Tenant
Step 5: Create an Azure Active Directory B2C tenant
Now we need to create the Azure Active Directory B2C tenant, for the mean time you can create it from the Azure Classic Portal and you will be able to manage all the settings from the new Azure Preview Portal.
- To start the creation process login to the classic portal and navigate to: New > App Services > Active Directory > Directory > Custom Create as the image below:
- A new popup will appear as the image below asking you to fill some information, note that if you selected one of the following countries (United States, Canada, Costa Rica, Dominican Republic, El Salvador, Guatemala, Mexico, Panama, Puerto Rico and Trinidad and Tobago) your Azure AD B2C will be Production-Scale tenant, as Azure AD B2C is GA only in the countries listed (North America). This will change in the coming months and more countries will be announced as GA. You can read more about the road map of Azure AD B2C here. Do not forget to check “This is a B2C directory” for sure 🙂
- After your tenant has been created, it will appear in the Active Directory extension bar, as the image below; select the tenant and click on “Configure” tab, then click on “Manage B2C Settings” as the image below. This will open the new Azure Preview Portal where we will start registering the App and managing policies there.
Step 6: Register our application in Azure AD B2C tenant
Now we need to register the application under the tenant we’ve created, this will allow us to add the sign-in, sign-up, edit profile features in our app, to do so follow the below steps:
- Select “Applications” from the “Settings” blade for the B2C tenant we’ve created, then click on the “Add” Icon on the top
- A new blade will open asking you to fill the following information
- Name: This will be the application name that will describe your application to consumers. In our case I have used “BitofTech Demo App”
- Web API/Web APP: we need to turn this on as we are protecting a Web Api and Web app.
- Allow implicit flow: We will turn this on as well as we need to use OpenId connect protocol to obtain an id token
- Reply URL: those are the registered URLs where the Azure Active Directory B2C will send the authentication response to (tokens) or error responses to. The client applications calling the API can specify the Reply URL, but it should be registered in the tenant by the administrator in order to work. In our case I will put the Reply URL now to the Web API URL which is “https://localhost:44339/” this will be good for testing purposes but in the next post I will add another URL for the Web application we will build to consume the API. As you notice you can register many Reply URLs so you can support different environments (Dev, staging, production, etc…)
- Native Client: You need to turn this on if you are building mobile application or desktop application client, for the mean time there is no need to turn it on as we are building web application (Server side app) but we will visit this again in the coming posts and enable this once we build a desktop app to consume the API.
- App key or App secret: This will be used to generate a “Client Secret” for the App which is needed to authenticate the App in the Authorization/Hybrid OAuth 2.0 flow. We will need this in the future posts once I describe how we can obtain access tokens, open id tokens and refresh tokens using Raw HTTP requests. For the mean time, there is no need to generate an App key.
- One you fill all the information, click “Save” and the application will be created and Application ID will be generated, copy this value and keep it on the notepad as we will use it later on.
- below an image which shows the App after filling the needed information:
Step 7: Selecting Identity Providers
Azure Active Directory B2C offers multiple social identity providers Microsoft, Google, Amazon, LinkedIn and Facebook in addition to the local App-specific accounts. The local account can be configured to use a “Username” or “Email” as a unique attribute for the account, we will use the “Email” and we will use only the local accounts in this tutorial to keep things simple and straight forward.
You can change the “Identity Providers” by selecting the “Identity providers” blade. This link will be helpful if you need to configure it.
Step 8: Add custom attributes
Azure AD B2C directory comes with a set of “built-in” attributes that represents information about the user, attributes such as (Email, First name, Last name, etc…) those attributes can be extended in case you needed to add extra information about the user upon signing up (creating a profile) or editing it.
At the mean time you can create an attribute and set the datatype for it as “String”, I believe that this limitation would be resolved in the coming releases.
To do so select “User attributes” blade and click on the “Add” icon, a new blade will open asking you to fill the attribute name, data type and description. In our case, I’ve added an attribute named “Gender” to capture the gender of the user during the registration process (profile creation or sign up). Below an image which represents this process:
We will see in the next steps how we can retrieve this custom attribute value in our application, there are 2 ways to do so, first one is to include it in claims encoded in the token and the second one is to use Azure AD Graph API. We will use the first method.
In the next step, I will show you how to include this custom attribute in the sign-up policy.
Step 9: Creating different policies
The unique thing about Azure Active Directory B2C is using the extensible policy framework, which allows the developers to define an easy and reusable way to build the identity experience that they want to provide for application consumers (end users). So for example to enroll a new user in your app and create a app-specific local account, you need to create a Signup Policy where you configure the attributes needed to capture it from the user, you configure the attributes (claims) you need to retrieve after successfully executing the policy, you can configure which identity providers consumers are allowed to use, as well you can configure the look and feel for the signup page by doing simple modifications such as changing label names, the order of the fields, or replace the UI entirety (more about this in future post). All this applies to other policies used to implement identity features such as signing in, editing profile.
As well by using the extensible policies framework we can create multiple policies of different types in our tenant and use them in our applications as needed. Policies can be reused across applications, as well they can be exported and uploaded for easier management. This allows us to define and modify identity experiences with minimal or no changes to application code.
Now let’s create the first policy which is the “Signup” policy which will build the experience for users during the signup process and I show you how to test it out. to do so follow the below steps:
- Select the “Sign-up” policies.
- Click on the “Add” icon at the top of the blade.
- Select a name for the policy, picking up a clear name is important as we will reference the name in our application, in our case I’ve used “signup”.
- Select the “Identity providers” and select “Email signup”. In our case this the only provider we have configured for this tenant so far.
- Select the “Sign-up” attributes. Now we have the chance to choose the attributes we want to collect from the user during the signup process. I have selected 6 attributes as the image below.
- Select the “Application claims”. Now we have the chance to choose the claims we want to return in the tokens sent back to out application after a successful signup process, remember that those claims are encoded within the token so do not get crazy about adding many claims as the token size will increase. I have selected 9 claims as the image below.
- Finally, click on “Create” button.
Notes:
- The policy that will be created will be named as “B2C_1_signup” all the policies will be prefixed by “B2C_1_” fragment, do not ask me why but it seems its implementation detail 🙂
- You can change the attribute label names (Surname -> Last Name) as well change the order of the fields by dragging the attributes, and set if the field is mandatory or not. Notice how I changed the custom attribute “Gender” to display as a drop down list and have a fixed items such as “Male” and “Female”. All this can be done by selecting the “Page UI customization” section.
- Once the policy has been created you can configure the ID token, and refresh token expiration date time by selecting the section “Toke, session & SSO config”. I will cover this in the coming posts, for now we will keep the defaults for all policies we will create, and you can read more about this here.
- Configuring ID token and refresh token expiration times is done pair policy not tenant, IMO I do not know why this was not done per tenant, not per policy, this for sure gives you better flexibility and finer grained control on how to manage policies, but I can not think of a use case where you want to have a different expiration dates for different policies. We will keep them the same for all policies we will create unless we are testing out something.
- Below an image on how to change the custom attribute “Gender” order between other fields as well how to the “User input type” to use Drop down list:
Step 10: Creating the Sign in and Edit Profile policies
I won’t bore you with the repeated details for creating the other 2 policies which will be using during this tutorial, they all follow the same approach I have illustrated in the previous step. Please note the below about the newly created policies:
- The policy which will be used to sign in the user (login) will be named “Signin“, so after creating it will be named “B2C_1_Signin“.
- The policy which will be used to edit the created profile will be named “Editprofile“, so after creating it will be named “B2C_1_Editprofile“.
- Do not forget to configure the Gender custom attribute for the “Editprofile” policy, as we need to display the values in the drop-down list instead of a text box.
- Select the same claims we have already selected for the signup policy (8 claims)
- You can click “Run now” button and test the new policies using a user that you already created from the sign up policy (Jump to next step before).
- At the mean time the only way to execute those policies and test them out in this post and the coming one is to use the “Run now” button until I build a web application which communicates with the Web API and Azure Active Directory B2C tenant.
Step 11: Testing the created signup policy in Azure AD B2C tenant
Azure Active Directory B2C provide us with the ability to test the policies locally without leaving the azure portal, to do so all you need to click on is the “Run now” button and select the preferred Reply URL in case you registered many Reply URL once you registered the App, in our case we will have only a single app and a single reply URL. The Reply URL will be used to return the Id token in hash fragment to the Reply URL selected.
Once you click “Run now” button a new window will open and you will be able to test the sign up policy by filling up the needed information, notice that you need to use a real email in order to send activation code to it and verify that you own this email, I believe the Azure AD team implemented by verifying the email before creating the account to avoid creating many unreal emails that will never get verified. Smart decision.
Once you receive the verification email with the six digit code, you need to enter it in the verification code text box and click on “verify”, if all is good the “Create” button is enabled and you can complete filling the profile. You can change the content of the email by following this link
The password policy (complexity) used here is the same one used in Azure Active Directory, you can read more about it here.
After you fill all the mandatory attributes as the image below click create and you will notice that a redirect took place to the Reply URL and there is an Id Token returned as a hash fragment. This Id token contains all the claims specified in the policy, you can test it out by using a JWT debugging tool such as calebb.net so if we tried to debug the token we’ve received after running the sign up policy, you will see all the claims we asked for encoded in this JWT token.
Notes about the claims:
- The newly “Gender” custom attribute we have added is returned under a claim named “extension_Gender“. It seems that all the custom attributed are prefixed by the phrase “extension”, I need to validate this with Azure AD team.
- The globally user unique identifier is returned in the claim named “oid”, we will depend on this claim value to distinguish between registered users.
- This token is generated based on the policy named “B2C_1_signup”, note the claim named “tfp”.
To have a better understanding of each claim meaning, please check this link.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
{ "exp": 1471954089, "nbf": 1471950489, "ver": "1.0", "iss": "https://login.microsoftonline.com/tfp/3d960283-c08d-4684-b378-2a69fa63966d/b2c_1_signup/v2.0/", "sub": "Not supported currently. Use oid claim.", "aud": "bc348057-3c44-42fc-b4df-7ef14b926b78", "nonce": "defaultNonce", "iat": 1471950489, "auth_time": 1471950489, "oid": "31ef9c5f-6416-48b8-828d-b6ce8db77d61", "emails": [ "ahmad.hasan@gmail.com" ], "newUser": true, "given_name": "Ahmad", "family_name": "Hasan", "extension_Gender": "M", "name": "Ahmad Hasan", "country": "Jordan", "tfp": "B2C_1_signup" } |
This post turned out to be longer than anticipated so I will complete in the coming post , in the next post where I will show you how to reconfigure Our Web Api project to rely on out Azure AD B2C IdP and validate those tokens.
Great Post, Thanks for sharing.
Have you ever created an api using the Azure Graph where you can create a new User using JSON feed? And which allows you to store a username that is a normal email address, for example testing@mysite.com?
Hello Jonathan, I didn’t do this before, but I will try it out and see if I’m successful in this, I will keep you posted and thanks for your comment 🙂
H All
@Jonathan, check this out.
https://azure.microsoft.com/en-us/documentation/articles/active-directory-b2c-devquickstarts-graph-dotnet/
Not as good as Taiser’s tutotials, but it works.
Best Regards.
I have a scenario where I am thinking of creating many B2C directories (one per tenant). Is it possible for these tenants to all share application registrations? From what I can currently see, that is not possible. When I add an App Registration I don’t see a way to specify a an existing mutli-tenant app (for example, one registered in another tenant). In other words, an App Registration always results in a different Application ID.
I guess I am wondering if I am trying to use B2C in a way that is not really intended…
Hi,
Great tutorial, however I run into a an issue.
When I redirect to my localhost I just get an error message “We cant reach the page”.
This makes sense to me since I have at no point linked up the web app with Azure, so they are not aware of each other, am I correct or should I not be getting this error? I’ve followed every step 🙂
Hello Kalle,
You are correct, you can’t use your localhost to connect to my B2C tenant if you are trying this, but if you have provisioned your tenant then you can do this for sure.
Let me know if you need further help 🙂
How Azure AD B2C is different from traditional Azure AD which we talk to using ADAL?
I’ve got a very strange error where after authentication from the AADB2C service, the redirect back to the application does not have the Request.IsAuthenticated set to true.
1) Start the app. It’s in Azure
2) Click Signin. App redirects to AADB2C.
3) Enter userid and password to AADB2C, which redirects back to app.
4) App continues to show the Signin option.
Once this happens, I must restart the application in Azure. After a restart, clicking the Signin now replies with the Username and the Signout option.
What could possibly be stored in the web app regarding the authenticated user that could break and a restart fix it? I’m stumped. I can’t deploy my application to production if AADB2C authentication doesn’t function reliably.
The application is a web app that was started from the web app example provided on one of the B2C information pages. B2C-WebApp-OpenIdConnect-DotNet-complete is the solution name.
However, I went through it to make sure app the packages are at the same version as yours. The code is almost identical to yours.
Evidently this problem is from a long-standing issue that has not been fixed.
http://katanaproject.codeplex.com/wikipage?title=System.Web%20response%20cookie%20integration%20issues&referringTitle=Documentation
I implemented the fix to the cookie handling and the problem has not happened since.
I used the sample projects of how to do this from Azure related sites. They don’t know about this issue or just haven’t bothered to update their code. This resulted in a month of wasted time trying to get their code to work when it was never going to work as coded. EOM.
Hi, can I have local account and also an identity provider, for example a Microsoft account?
In our application, we are using Username instead of Email for login to Azure B2C. Can’t we retrieve email id that was entered by using during signup?
Are you aware of a way to enable a flow like this:
1) User clicks “Sign In”
2) User presented with primary Sign In UI but given a link titled something like “Don’t have an account?”
– Key to enable this for both initial sign in and after failed sign in
Without this, users can get stuck without a reasonable way to get to the “Sign Up” link. User would have to do something like hit back until they get to the underlying website, or reenter the original URL.
Thanks for any pointers.
Hi,
I have followed you code, however I am getting 401 when testing as per above. Please can you point me in the right direction for decoding.
I have checked your website and i have found some duplicate content, that’s why you don’t rank high in google,
but there is a tool that can help you to create 100% unique articles, search for: Best article rewritwer
Ercannou’s essential tools
Hi Mate
Is there a way to hide all the input fields below verification code – until verification is completed?
It is driving our new users away as many aren’t familiar with 2FA or waiting for verification codes to email?