Creating Custom Login Page in Azure AD B2C

Creating Custom Login Page in Azure AD B2C

Few months back, I was going through the MSAL and how Azure AD can be used with different types of applications, how authentication can be configured, etc.

This is an extension to the MSAL series posts written few months back. If you want to go through the previous posts in MSAL series, below are the links for ready reference.

Why this post ?

I received quite some comments mails about the MSAL series and how it is helping people. Some of the readers asked this question – can I use the Azure AD MSAL for authentication and still use my custom login page ? There is also stackoverflow question asking the same thing.

I have already covered in one of the posts in the series that the login pages are by default provided by Azure AD service. Those login pages can be customized and the brand information, colors, background images can be changed to make the look and feel similar to your web application. All those configurations are very easy and can be done very quickly.

If this is not sufficient, then you can go for having a custom login page. But obviously, in this case, the efforts required would be higher than just customizing the default pages. If you still want to go for it, then this post would cover the step by step information on how to set it up.

Please note that you should always have a look at the customization options provided by the the Company branding feature. Most of the customizations can be done using that feature as explained in my previous blog post. If this feature is not helping you to achieve the intended outcome, then only you should try the next steps.

Prerequisites

For this demo, you will need Azure Subscription. If you don’t have an Azure subscription, create a free account before you begin.

Also, you can refer the article – Protect .NET Core Angular App with Azure AD B2C – to setup the Azure AD B2C and a web application which is protected by that AD instance.

The Idea

The idea is to have a custom HTML page instead of Azure AD’s login page, so that the consumer should be able to modify look and feel, theme etc. so that the page looks very similar to consumer’s site.

Please note that it does not cover showing login page as popup on the site.

So, let’s get started !

Custom HTML Rules

In this section, we are going to see how to create a custom HTML for the sign-in page. But before directly jumping into steps of creating the HTML, let’s first understand the rules to be taken care of.

  • The HTML can be either static HTML or it can also be a dynamic page created using .NET, PHP, etc.
  • The page can include CSS and JS, but it cannot include insecure elements like iframes, frames, or forms..
  • The only requirement from Azure AD is there should be an empty div element, with id="api"
  • You can host your new page on any public service, that supports HTTPS and CORS.
  • Using page layout version 1.2.0 and above, you can add the data-preload="true" attribute in your HTML tags to control the load order for CSS and JavaScript.  This would help in avoiding the flickering issues.
  • Safari, Mozilla, Chrome and Edge are the main supported browsers. For details about versions refer the documentation.
  • It is recommended to start from the source code of Azure AD B2C pages, host them, get them working, and then build on top of this to ensure everything works. Refer the exception.html for the source code of default pages.

High Level Steps

Below are high level steps for our intended setup:

  • Develop: Download and customized the default file
  • Prepare Host: We will use storage account for hosting the static HTML page. Once you understand the conceptual implementation, it should be easy to extend the concept and host the page anywhere publicly available CORS enabled HTTPS endpoint.
  • Publish the custom HTML file from step 1 to publicly available CORS enabled HTTPS endpoint.
  • CORS: Set cross-origin resource sharing (CORS) for your web app.
  • Update User Flow: Point your policy to your custom policy content URI.

Develop

I will skip explaining the below code. It is a HTML / CSS / JS combo with default div with id set to api. Just copy this to start setting up the custom page.

<html>
<head>
<style>
@import url(https://fonts.googleapis.com/css?family=Dancing+Script|Roboto);
*,
*:after,
*:before {
box-sizing: border-box;
}
body {
background-image: linear-gradient(to right top, #7f3860, #793569, #703473, #62357e, #4d3888, #3f4595, #2c519f, #005ca8, #0071b0, #0084ae, #0094a5, #0ca299);
text-align: center;
font-family: 'Roboto', sans-serif;
}
.panda {
position: relative;
width: 200px;
margin: 50px auto;
}
.face {
width: 200px;
height: 200px;
background: #fff;
border-radius: 100%;
margin: 50px auto;
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.15);
z-index: 50;
position: relative;
}
.ear,
.ear:after {
position: absolute;
width: 80px;
height: 80px;
background: #000;
z-index: 5;
border: 10px solid #fff;
left: -15px;
top: -15px;
border-radius: 100%;
}
.ear:after {
content: '';
left: 125px;
}
.eye-shade {
background: #000;
width: 50px;
height: 80px;
margin: 10px;
position: absolute;
top: 35px;
left: 25px;
transform: rotate(220deg);
border-radius: 25px/20px 30px 35px 40px;
}
.eye-shade.rgt {
transform: rotate(140deg);
left: 105px;
}
.eye-white {
position: absolute;
width: 30px;
height: 30px;
border-radius: 100%;
background: #fff;
z-index: 500;
left: 40px;
top: 80px;
overflow: hidden;
}
.eye-white.rgt {
right: 40px;
left: auto;
}
.eye-ball {
position: absolute;
width: 0px;
height: 0px;
left: 20px;
top: 20px;
max-width: 10px;
max-height: 10px;
transition: 0.1s;
}
.eye-ball:after {
content: '';
background: #000;
position: absolute;
border-radius: 100%;
right: 0;
bottom: 0px;
width: 20px;
height: 20px;
}
.nose {
position: absolute;
height: 20px;
width: 35px;
bottom: 40px;
left: 0;
right: 0;
margin: auto;
border-radius: 50px 20px/30px 15px;
transform: rotate(15deg);
background: #000;
}
.body {
background: #fff;
position: absolute;
top: 200px;
left: -20px;
border-radius: 100px 100px 100px 100px/126px 126px 96px 96px;
width: 250px;
height: 382px;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.3);
}
.hand,
.hand:after,
.hand:before {
width: 40px;
height: 30px;
border-radius: 50px;
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.15);
background: #000;
margin: 5px;
position: absolute;
top: 70px;
left: -25px;
}
.hand:after,
.hand:before {
content: '';
left: -5px;
top: 11px;
}
.hand:before {
top: 26px;
}
.hand.rgt,
.rgt.hand:after,
.rgt.hand:before {
left: auto;
right: -25px;
}
.hand.rgt:after,
.hand.rgt:before {
left: auto;
right: -5px;
}
.foot {
top: 360px;
left: -80px;
position: absolute;
background: #000;
z-index: 1400;
box-shadow: 0 5px 5px rgba(0, 0, 0, 0.2);
border-radius: 40px 40px 39px 40px/26px 26px 63px 63px;
width: 82px;
height: 120px;
margin-top: 135px;
}
.foot:after {
content: '';
width: 55px;
height: 65px;
background: #222;
border-radius: 100%;
position: absolute;
bottom: 10px;
left: 0;
right: 0;
margin: auto;
}
.foot .finger,
.foot .finger:after,
.foot .finger:before {
position: absolute;
width: 25px;
height: 35px;
background: #222;
border-radius: 100%;
top: 10px;
right: 5px;
}
.foot .finger:after,
.foot .finger:before {
content: '';
right: 30px;
width: 20px;
top: 0;
}
.foot .finger:before {
right: 55px;
top: 5px;
}
.foot.rgt {
left: auto;
right: -80px;
}
.foot.rgt .finger,
.foot.rgt .finger:after,
.foot.rgt .finger:before {
left: 5px;
right: auto;
}
.foot.rgt .finger:after {
left: 30px;
right: auto;
}
.foot.rgt .finger:before {
left: 55px;
right: auto;
}
.form {
display: none;
max-width: 400px;
padding: 20px 40px;
background: #fff;
height: 400px;
margin: auto;
display: block;
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.15);
transition: 0.3s;
position: relative;
transform: translateY(-100px);
z-index: 500;
border: 1px solid #eee;
}
.form .up {
transform: translateY(-180px);
}
h1 {
color: #FF4081;
font-family: 'Dancing Script', cursive;
}
.btn {
background: #fff;
padding: 5px;
width: 150px;
height: 35px;
border: 1px solid #FF4081;
margin-top: 25px;
cursor: pointer;
transition: 0.3s;
box-shadow: 0 50px #FF4081 inset;
color: #fff;
}
.btn:hover {
box-shadow: 0 0 #FF4081 inset;
color: #FF4081;
}
.btn:focus {
outline: none;
}
</style>
</head>
<body>
<div class="panda">
<div class="ear"></div>
<div class="face">
<div class="eye-shade"></div>
<div class="eye-white">
<div class="eye-ball"></div>
</div>
<div class="eye-shade rgt"></div>
<div class="eye-white rgt">
<div class="eye-ball"></div>
</div>
<div class="nose"></div>
<div class="mouth"></div>
</div>
<div class="body"> </div>
<div class="foot">
<div class="finger"></div>
</div>
<div class="foot rgt">
<div class="finger"></div>
</div>
</div>
<div class="form">
<div class="hand"></div>
<div class="hand rgt"></div>
<h1>Panda Login</h1>
<div id="api"></div>
</div>
</body>
</html>

If you open the HTML file in browser, it should be rendered as shown in below snapshot:

Locally viewing the custom HTML
Locally viewing the custom HTML

Prepare Host – Storage Account & Container

We will use simplest method – storage accounts and blob containers to host the web page. Please refer this previous blog post for step by step guide on how to create a storage account and a container.

Below snapshot shows important inputs on first screen of wizard. Rest of the inputs can be left to their default values.

Azure Portal: Create new storage account
Azure Portal: Create new storage account

Once storage account is created, create a blob container with name root. Below snapshot highlights the inputs required for this container creation. Please make sure you select public access level as shown in the snapshot.

Azure Portal: Create new container
Azure Portal: Create new container

Publish

Now, upload the file to the root container that we have created in previous step.

For this, you will have to open the container by clicking on it and then on the new panel, select Upload button from the Overview panel as shown below.

Clicking on upload will open a new right side panel, where you can select the file and keep rest of the inputs to their default values. Then click on Upload button on right hand side panel.

Azure Portal: upload a blob to the container
Azure Portal: upload a blob to the container

CORS

Now, let’s navigate to the storage account that you have created in previous step. Then select CORS option from the left side navigation and then enter below inputs:

  • Allowed Origins, https://tenant-name.b2clogin.com. Please replace tenant-name with the name of your Azure AD. Ensure that there is no trailing slash at the end of input URL.
  • Allowed Methods, select two options from dropdown GET and OPTIONS
  • Allowed Headers, enter ‘*
  • Exposed Headers, enter ‘*
  • Max Age, provide the value 200.

Once these inputs are provided, click on Save button to save the settings.

Azure Portal: set CORS for the storage account
Azure Portal: set CORS for the storage account

For testing purpose, let’s add another entry.

Repeat the same steps to add another entry in the CORS settings table. This time use https://www.test-cors.org as the Allowed Origins url and rest of the inputs should be same as in previous row.

The site http://www.test-cors.org allows you to send the XHR request to local or remote URL.

Navigate to https://www.test-cors.org and enter the remote URL of your HTML page. The URL of HTML page would be something like https://storage-account-name.blob.core.windows.net/root/my-login-template.html, where storage-account-name should be replaced by the actual name of the storage account. Then hit the Send Request button.

This should return XHR Status: 200 in the response. If it is not 200 OK response, then you might have missed a step in CORS configuration so validate your settings before proceeding further.

Update User Flow

If you do not know about user flows, I would suggest to please go through this post to get the basic concepts.

So, switch to the Azure AD B2C directory now and search for Azure AD B2C in the search box and select the entry. Then select User Flows under Policies. Select the user flow which is applicable for the site and then select Page layouts menu option from the next page.

In the user flow page layouts, select Unified sign up or sign in page entry and then provide below inputs:

Let other inputs as they are. Then click on Save button.

Azure Portal: Update user flow page layouts
Azure Portal: Update user flow page layouts

Verify

To verify the changes, you can either run the user flow from edit page layout screen OR you can try to access the protected application via browser. If you are not logged in to it, the Azure AD would show our custom html on the login page.

The login page would look something similar to the below snapshot.

Azure AD B2C: Custom HTML for unified sign in page.
Azure AD B2C: Custom HTML for unified sign in page.

Yay, we have successfully configured custom HTML for the Azure AD B2C login page. The sign in section is still not fully styled, but I hope you have got the conceptual idea.

As recommended in the documentation, you can start with the HTML and CSS of the default pages and then build up on that template to achieve the intended result.

I hope you liked this new article from the Azure AD series. Let me know your thoughts.

Leave a Reply