Azure AD B2C logouts and redirection URLs
Background
I’ve been helping a client build a customer-facing NodeJS web application which leveraged Azure AD B2C as its identity provider. Things were going well with the development and Azure AD B2C served them really well. It’s cost-effective and gives them all the controls and security features they’ve come to expect with Azure AD (the non-B2C version). As any responsible company, they run penetration tests on the application prior to releasing to production and they identified one item that can pose as a security threat.
As part of the security audit, they noticed upon logging out of the application the post_logout_redirect_uri
parameter in the URL controlled where the user is “dropped off” after logging out. This became an area of concern. The concern here is that if the application gets compromised, a malicious actor can inject an unauthorized url into the request object and take the user to a “spoofed” login page and ultimately collect login credentials unknowingly from the user. This sort of attack can be categorized as an Input Capture technique within the MITRE ATT&CK framework. There are a few stack overflow posts that discusses this topic of an open redirect.
- Azure AD B2C vulnerable to Open Redirect? - Stack Overflow
- single sign on - Azure AD B2C error ‘id_token_hint parameter not specified’ when EnforceIdTokenHintOnLogout=“true” in signin_signup custom policy - Stack Overflow
An argument made that if someone were to be able to modify the URL from within the app, there are bigger security concerns than what we are facing here. Nonetheless, it will be a good idea to implement some security measures using Azure AD B2C to ensure users are only redirected to “approved” URLs. So lets dive in.
Solution
This doc states the following:
After logout, the user is redirected to the URI specified in the
post_logout_redirect_uri
parameter, regardless of the reply URLs that have been specified for the application. However, if a valid id_token_hint is passed, and the Require ID Token in logout requests is turned on, Azure AD B2C verifies that the value ofpost_logout_redirect_uri
matches one of the application’s configured redirect URIs before performing the redirect. If no matching reply URL was configured for the application, an error message is displayed and the user is not redirected.To set the required ID Token in logout requests, see Configure session behavior in Azure Active Directory B2C.
So there are two things you need to do:
- Configure your sign-up/sign-in user flow to require an
id_token_hint
on logout - Pass in the
id_token_hint
on logout
For this walkthrough, I’ve setup a basic Azure AD B2C tenant and configured an “out-of-the-box” sign-up and sign-in user flow.
If you don’t have an Azure AD B2C tenant yet and want to follow along, go through this step to set up your tenant, then this step to create a new app registration, and finally this step to configure a sign-up and sign-in user flow and come back.
The application registration details will be used in our sample JavaScript application in a section below.
Azure AD B2C configuration
To configure your Azure AD B2C user flow:
-
Open your Azure AD B2C resource in the B2C tenant
-
Click on User flows in the left nav
-
Click on your sign-up/sign-in user flow from the list of user flows
-
Click on Properties
-
Scroll down to Session behavior and set the “Require ID Token in logout requests” radio button to Yes
That’s it from the Azure AD side. Easy, right?!?
Add id_token
to your JavaScript application
To configure your app to retrieve the id_token and send the id_token on logout request
-
Clone this sample application then open it using VSCode
git clone https://github.com/Azure-Samples/ms-identity-b2c-javascript-spa.git
-
Since we’ll be using a JavaScript application to test with, make sure you update your application registration and add a “Single-page application” as a platform and update the Redirect URI to include the JavaScript app’s URL (http://localhost:6420). This is the URL that Azure AD will use to validate against when logging a user out.
-
Update
apiConfig.js
with your Azure AD B2C tenant name -
Update
authConfig.js
with your Azure AD B2C app registration client id -
Update
policies.js
file with your Azure AD B2C tenant name and policy names -
Update the
authRedirect.js
file
So, what we did here is configure our application to point to our Azure AD B2C tenant and application registration. Then we updated some of the MSAL.js code to retrieve include a new id_token
variable, store it, and use it on the logout request.
Now, the application will send the id_token
as part of the logout request and this satisfies the requirement we introduced by setting the property in our “sign-up/sign-in” user flow.
Let’s test it now.
Test the solution
To test, let’s run the app:
-
Run
npm start
-
Open a browser window and navigate to http://localhost:6420
-
Click the Sign-in button
-
Open F12 browser tools
-
Click the Network tab
-
Check the Preserve log box
-
Click the Sign out button
-
In the F12 tools, click on the logout URL request and copy the Request URL
-
Using a text editor, modify the URL and set the
post_logout_redirect
to a website that is not included in your app registration’s list of redirect URIs. Here is my sample URL.https://securedog.b2clogin.com/securedog.onmicrosoft.com/b2c_1_susi/oauth2/v2.0/logout?post_logout_redirect_uri=https://www.google.com&client-request-id=99f11b24-b3dd-4ab0-b7e7-e3fe6fac2854&id_token_hint=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1jNTdkTzZRR1RWQndhTmsifQ.eyJleHAiOjE2NTMyNjk0NTgsIm5iZiI6MTY1MzI2NTg1OCwidmVyIjoiMS4wIiwiaXNzIjoiaHR0cHM6Ly9zZWN1cmVkb2cuYjJjbG9naW4uY29tLzBhODk1MTgxLWEyNzYtNDRjOC04NDlkLTI1ODFlN2RiNGJhYS92Mi4wLyIsInN1YiI6ImM2NmYxMDQ4LTEyZDQtNDVlMy1iMzIwLTI3M2Q1ZGVhNjVjYyIsImF1ZCI6IjI2MzM5NzkzLTIyZjItNDI3ZS05NmEzLWZlMTkwMDlhODRlZSIsIm5vbmNlIjoiMmQxZmRkZWItNTc1ZS00NTRlLTgzMTQtYzM0NjdlZDM0ZWEzIiwiaWF0IjoxNjUzMjY1ODU4LCJhdXRoX3RpbWUiOjE2NTMyNjU4NTcsIm9pZCI6ImM2NmYxMDQ4LTEyZDQtNDVlMy1iMzIwLTI3M2Q1ZGVhNjVjYyIsIm5hbWUiOiJQYXVsIiwidGZwIjoiQjJDXzFfc3VzaSJ9.KSaRAfIxnICglfg9Bm04XElKjdV5SC4RTVD-BBDueuaqMj5PJM7RRnPZjUgqyVPBSRDdqI90fHSuGid-xmKe3t8bNhhvg-zfkvfuyIrc7fa1e7u5j7QYMkn7V6zcojOo92s6pNZ0aPVKz0WaKAG2yq0sMWLtZtvm4v2vbQn9T3Hzv-F4FpK-9blOvqvVnbk7_hPreouSTykTICMIdEmnSEXt1d0ttXHHyXX9A2ieoRtCTuZe7sJjYPMpw9gJoYum4jTvCu2agPA8yu7K4WkElErUcN7tUmm-VrEdcuoJSg-Y6HCr6WLN4KZOuAVobPZlrsiX0ZmA7DRzkORwjtHWyA
Here we are attempting to redirect the user to https://google.com which is NOT included in our list of application redirect URIs
-
You should be signed out, click the Sign-in button again
-
Now, paste in the modified URL into a new browser tab
-
You should see the following image
-
If your requested redirect URI is not in the list of Redirect URIs then the logout attempt will fail
Summary
To recap, in order to enforce the validation of the post_logout_redirect_uri
parameter value against what you have configured in your application registration’s redirect URIs, you must set your user flow to require an id_token
on logout then update your application to pass store the id_token
and pass it back when making a request to log out.
I only walked through updating the code in the authRedirect.js
file which is meant to redirect users in place during the authentication process. If you’d like to use pop-up pages instead for user authentication, you can go ahead and update the authPopup.js
file in the same manner as we did above.
This should keep your security team happy.