services | platforms | author | level | client | endpoint |
---|---|---|---|---|---|
azure-active-directory-B2C |
nodejs |
soumi |
200 |
NodeJs WebApp |
Microsoft Identity Platform |
A NodeJS WebApp signing-in users with the Microsoft Identity Platform in Azure AD B2C using the Embedded sign-in experience using iframe
This sample shows how to build a NodeJS Express Web app that uses MSAL-Node to implement OpenID Connect to sign in users in Azure AD B2C using the embedded sign-in experience. It assumes you have some familiarity with Azure AD B2C. If you'd like to learn all that B2C has to offer, start with our documentation at https://aka.ms/aadb2c.
Try out the working sample: B2C-MSAL App
This sample demonstrates a confidential client application registered on Azure AD B2C. It uses:
-
OIDC Connect protocol to implement standard B2C Custom Policies to:
- sign-up/sign-in a user
- reset/recover a user password
- edit a user profile
-
Authorization code grant to acquire an Access Token to call a protected web API (also on Azure AD B2C)
- Create an Azure Active Directory B2C tenant
- Register a web application in Azure Active Directory B2C
- Use the Custom Policies from the
policies
folder.
From your shell or command line:
git clone https://github.com/souravmishra-msft/B2CEmbeddedSigninMSAL.git
Navigate to the "B2CEmbeddedSigninMSAL-main"
folder
cd "B2CEmbeddedSigninMSAL-main"
To successfully use this sample, you need a working installation of Node.js.
Next, install the NPM.
From your shell or command line:
$ npm install
If you don't have an Azure AD B2C tenant yet, you'll need to create an Azure AD B2C tenant by following the Tutorial: Create an Azure Active Directory B2C tenant.
To use Azure AD B2C Embedded sign-in, you need to first enable a custom domain with your Azure AD B2C account, and configure it with a custom domain which is on the same domain as your published application, i.e. Same Origin. This is required because Azure AD B2C should not allow you to load the sign-in experience in an iframe unless you enable CORS for the source domains loading the iframe using a custom policy. So first, enable a custom domain similar to login.yourcustomdomain.com if your application is published on something similar to www.yourcustomdomain.com. To read more on enabling custom domains for B2C please check Tutorial: Enable custom domains for Azure Active Directory B2C.
Note: This will effectively disallow you from using sign-in if the application is run from a development machine. To get around that, you may want to create a separate Azure AD Tenant for development, with a development version of the custom policy which has CORS enabled for https://localhost:XXXX. Also note that this may result in an undesirable user experience Because Azure AD B2C session cookies within an iframe are considered third-party cookies, certain browsers (for example Safari or Chrome in incognito mode) either block or clear these cookies. This should never be done in your production Azure AD B2C tenant.
This sample depends on a custom sign-up/sign-in policy. This is because you need to customize the policy to allow JourneyFraming, where you allow the user journey to be loaded into an iframe. You can use one of the policies in the Azure AD B2C Custom Policy Starter Pack. Follow the Tutorial: Create user flows in Azure Active Directory B2C to deploy such policy.
In your policy, aside from the normal steps in customizing the policy, make sure to add the following in SignUpOrSignIn.xml, PasswordReset.xml, and ProfileEdit.xml, inside the <RelyingParty>
tag, right after the <DefaultUserJourney>
tag.
<!--
<RelyingParty>
<DefaultUserJourney ReferenceId="SignUpOrSignIn" /> -->
<UserJourneyBehaviors>
<JourneyFraming Enabled="true" Sources="https://yourcustomdomain.com https://www.yourcustomdomain.com" />
</UserJourneyBehaviors>
<!--
</RelyingParty> -->
Note: If you want to allow the custom policies to be used with your development environment, make sure you add https://localhost:xxxx to the list of sources in the `' tag.
Additionally, make sure the PublicPolicyUri
element in the <TrustFrameworkPolicy>
in all policy xml files (including TrustedFramewordBase.xml and TrustedFrameworkExtensions.xml) is referncing your custom domain, not the instance name. The example below is for SignUpOrSignin.xml.
PublicPolicyUri="http://login.yourcustomdomain.com/B2C_1A_signup_signin"
Now you need to register your web app in your B2C tenant, so that it has its own Application ID.
Your web application registration should include the following information:
- Enable the Web App/Web API setting for your application.
- Set the Reply URL to
https://www.yourcustomdomain.com/auth/openid/return
. - Copy the Application ID generated for your application, so you can use it in the next step.
- Open the sample in Visual Studio Code.
- In
policies.js
, we create ab2cPolicies
object to store authority strings for initiating each user-flow. The object may look like the following:
const b2cPolicies = {
names: {
signUpSignIn: "B2C_1A_EMBEDDEDSIGNIN_SIGNUP_SIGNIN",
resetPassword: "B2C_1A_EMBEDDEDSIGNIN_PASSWORDRESET",
editprofile: "B2C_1A_EMBEDDEDSIGNIN_PROFILEEDIT",
},
authorities: {
signUpSignIn: {
authority: "https://login.yourcustomdomain.com/yourcustomdomain.com/B2C_1A_EMBEDDEDSIGNIN_SIGNUP_SIGNIN",
},
resetPassword: {
authority: "https://login.yourcustomdomain.com/yourcustomdomain.com/B2C_1A_EMBEDDEDSIGNIN_PASSWORDRESET",
},
editprofile: {
authority: "https://login.yourcustomdomain.com/yourcustomdomain.com/B2C_1A_EMBEDDEDSIGNIN_PROFILEEDIT",
},
},
authorityDomain: "yourcustomdomain.com",
destroySessionUrl: "https://login.yourcustomdomain.com/yourcustomdomain.com/oauth2/v2.0/logout?p=B2C_1A_EMBEDDEDSIGNIN_SIGNUP_SIGNIN" + "&post_logout_redirect_uri=https://yourcustomdomain.com/"
}
- In
index.js
(can be found inside theconfig
folder), we setup the configuration object expected by MSAL NodeconfidentialClientApplication
class constructor:
const confidentialClientConfig = {
auth: {
clientId: "ENTER_CLIENT_ID",
authority: policies.authorities.signUpSignIn.authority, //signUpSignIn policy is our default authority
clientSecret: "ENTER_CLIENT_SECRET",
knownAuthorities: [policies.authorityDomain], // mark your tenant's custom domain as a trusted authority
redirectUri: "http://localhost:3000/redirect",
},
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
}
}
};
- Find the instances of
yourcustomdomain.com
and replace the value with your Azure AD B2C domain name. For example,constoso.com
- Find the assignment for
ENTER_CLIENT_ID
and replace the value with the Application ID from Step 4.
Note: You may find a JavaScript error
Blocked autofocusing on a <input> element in a cross-origin subframe.
in your browser's console. This is because the default behavior of the sign-in experience attempts to focus the user input on the first available user input in the sign-in page, which is not allowed inside an iframe for security reasons. You may want to attempt to override this behavior in your custom UI.
Blocked autofocusing on a <input> element in a cross-origin subframe.
- To locally run the sample, you can use
npm run start
or in dev environment you can usenpm run start-dev
. - You can also upload this sample as an App Service and configure the App Service to use HTTPS.
- If you don't have an account registered on the Azure AD B2C used in this sample, follow the sign up process. Otherwise, input the email and password for your account and click on Sign in.
In index.ejs, note the modal dialog definition:
<div class="modal fade" id="loginModal" tabindex="-1" aria-labelledby="loginModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="loginModalLabel"><img src="/images/CloudLogo.gif" width="50" height="50" > B2C Login</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div id="frameWrap">
<img id="loader1" src="/images/spinner.gif" width="100" height="100" alt="loading gif" />
</div>
<iframe id="loginFrame" frameborder="0" src="about:blank" data-isloaded="0" ></iframe>
</div>
</div>
</div>
We need to show the modal dialog which contains the iframe when the sign-in button is clicked. We do not need to worry about closing it when the login, is complete, this will be handled when we reload the entire page post-login in the next step.
<!-- Shows the loginModal when the SignIn/SignUp button is clicked-->
<script>
$("#signIn").on("click", function () {
$("#loginModal").modal("show");
$('#loginFrame').on('load', function () {
$('#loader1').hide();
});
$('#loginFrame').attr('src', '/login');
});
</script>
Because all the login experience and redirects happen inside the iframe, we need to identify when the user journey is completed, and the user is directed back to our application. We track the document.referrer value. If we're coming back form our login domain, we know the user journey is compelted, and we can reload the main window (not the iframe) so it reflects the logged-in user's details.
<!-- Because the authorization flow happens inside the iframe, we need to reload the main page.-->
<script>
var iframe = document.getElementById('loginFrame');
iframe.onload = function() {
var iframeBody = this.contentDocument.body;
if(iframeBody) {
window.top.location.reload();
}
}
</script>
Note: There are other alternatives here which we can do to accomplish the same logic, one being creating your own SignIn action which includes generating your own sign-in challenge and using the state parameter of the sign-in request using OpenIdConnect Events to indicate the redirect url. However, for the purposes of demo, this JavaScript snippet will do.
Did the sample not work for you as expected? Did you encounter issues trying this sample? Then please reach out to us using the GitHub Issues page. Consider taking a moment to share your experience with us.