Applies to:
Workforce tenants
External tenants (learn more)
Learn how to add sign-in to the code for your web app that signs in users. Then, learn how to let them sign out.
Sign-in
Sign-in consists of two parts:
- The sign-in button on the HTML page
- The sign-in action in the code-behind in the controller
In ASP.NET Core, for Microsoft identity platform applications, the Sign in button is exposed in Views\Shared\_LoginPartial.cshtml
(for an MVC app) or Pages\Shared\_LoginPartial.cshtm
(for a Razor app). It's displayed only when the user isn't authenticated. That is, it's displayed when the user hasn't yet signed in or has signed out. On the contrary, The Sign out button is displayed when the user is already signed-in. The Account controller is defined in the Microsoft.Identity.Web.UI NuGet package, in the Area named MicrosoftIdentity
<ul class="navbar-nav">
@if (User.Identity.IsAuthenticated)
{
<li class="nav-item">
<span class="navbar-text text-dark">Hello @User.Identity.Name!</span>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignOut">Sign out</a>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignIn">Sign in</a>
</li>
}
</ul>
In ASP.NET MVC, the Sign in button is exposed in Views\Shared\_LoginPartial.cshtml
. It's displayed only when the user isn't authenticated. That is, it's displayed when the user hasn't yet signed in or has signed out.
@if (Request.IsAuthenticated)
{
// Code omitted code for clarity
}
else
{
<ul class="nav navbar-nav navbar-right">
<li>@Html.ActionLink("Sign in", "SignIn", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })</li>
</ul>
}
In the Java quickstart, the sign-in button is located in the main/resources/templates/index.html file.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>HomePage</title>
</head>
<body>
<h3>Home Page</h3>
<form action="/msal4jsample/secure/aad">
<input type="submit" value="Login">
</form>
</body>
</html>
In the Node.js quickstart, the code for the sign-in button is located in index.hbs template file.
<p>Welcome to {{title}}</p>
<a href="/auth/signin">Sign in</a>
This template is served via the main (index) route of the app:
var express = require('express');
var router = express.Router();
router.get('/', function (req, res, next) {
res.render('index', {
title: 'MSAL Node & Express Web App',
isAuthenticated: req.session.isAuthenticated,
username: req.session.account?.username,
});
});
In the Python quickstart, the code for the sign-in link is located in login.html template file.
<ul><li><a href='{{ auth_uri }}'>Sign In</a></li></ul>
When an unauthenticated user visits the home page, the index
route in app.py redirects the user to the login
route.
@app.route("/")
def index():
if not (app.config["CLIENT_ID"] and app.config["CLIENT_SECRET"]):
# This check is not strictly necessary.
# You can remove this check from your production code.
return render_template('config_error.html')
if not auth.get_user():
return redirect(url_for("login"))
return render_template('index.html', user=auth.get_user(), version=identity.__version__)
The login
route figures out the appropriate auth_uri
and renders the login.html template.
@app.route("/login")
def login():
return render_template("login.html", version=identity.__version__, **auth.log_in(
scopes=app_config.SCOPE, # Have user consent to scopes during log-in
redirect_uri=url_for("auth_response", _external=True), # Optional. If present, this absolute URL must match your app's redirect_uri registered in Azure Portal
))
SignIn
action of the controller
In ASP.NET, selecting the Sign-in button in the web app triggers the SignIn
action on the AccountController
controller. In previous versions of the ASP.NET Core templates, the Account
controller was embedded with the web app. That's no longer the case because the controller is now part of the Microsoft.Identity.Web.UI NuGet package. See AccountController.cs for details.
This controller also handles the Azure AD B2C applications.
In ASP.NET, sign in is triggered from the SignIn()
method on a controller (for instance, AccountController.cs#L16-L23). This method isn't part of the .NET Framework (contrary to what happens in ASP.NET Core). It sends an OpenID sign-in challenge after proposing a redirect URI.
public void SignIn()
{
// Send an OpenID Connect sign-in request.
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" }, OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
In Java, sign out is handled by calling the Microsoft identity platform logout
endpoint directly and providing the post_logout_redirect_uri
value. For details, see AuthPageController.java#L30-L48.
@Controller
public class AuthPageController {
@Autowired
AuthHelper authHelper;
@RequestMapping("/msal4jsample")
public String homepage(){
return "index";
}
@RequestMapping("/msal4jsample/secure/aad")
public ModelAndView securePage(HttpServletRequest httpRequest) throws ParseException {
ModelAndView mav = new ModelAndView("auth_page");
setAccountInfo(mav, httpRequest);
return mav;
}
// More code omitted for simplicity
When the user selects the Sign in link, which triggers the /auth/signin
route, the sign-in controller takes over to authenticate the user with Microsoft identity platform.
login(options = {}) {
return async (req, res, next) => {
/**
* MSAL Node library allows you to pass your custom state as state parameter in the Request object.
* The state parameter can also be used to encode information of the app's state before redirect.
* You can pass the user's state in the app, such as the page or view they were on, as input to this parameter.
*/
const state = this.cryptoProvider.base64Encode(
JSON.stringify({
successRedirect: options.successRedirect || '/',
})
);
const authCodeUrlRequestParams = {
state: state,
/**
* By default, MSAL Node will add OIDC scopes to the auth code url request. For more information, visit:
* https://docs.microsoft.com/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes
*/
scopes: options.scopes || [],
redirectUri: options.redirectUri,
};
const authCodeRequestParams = {
state: state,
/**
* By default, MSAL Node will add OIDC scopes to the auth code request. For more information, visit:
* https://docs.microsoft.com/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes
*/
scopes: options.scopes || [],
redirectUri: options.redirectUri,
};
/**
* If the current msal configuration does not have cloudDiscoveryMetadata or authorityMetadata, we will
* make a request to the relevant endpoints to retrieve the metadata. This allows MSAL to avoid making
* metadata discovery calls, thereby improving performance of token acquisition process. For more, see:
* https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/performance.md
*/
if (!this.msalConfig.auth.cloudDiscoveryMetadata || !this.msalConfig.auth.authorityMetadata) {
const [cloudDiscoveryMetadata, authorityMetadata] = await Promise.all([
this.getCloudDiscoveryMetadata(this.msalConfig.auth.authority),
this.getAuthorityMetadata(this.msalConfig.auth.authority)
]);
this.msalConfig.auth.cloudDiscoveryMetadata = JSON.stringify(cloudDiscoveryMetadata);
this.msalConfig.auth.authorityMetadata = JSON.stringify(authorityMetadata);
}
const msalInstance = this.getMsalInstance(this.msalConfig);
// trigger the first leg of auth code flow
return this.redirectToAuthCodeUrl(
authCodeUrlRequestParams,
authCodeRequestParams,
msalInstance
)(req, res, next);
};
}
redirectToAuthCodeUrl(authCodeUrlRequestParams, authCodeRequestParams, msalInstance) {
return async (req, res, next) => {
// Generate PKCE Codes before starting the authorization flow
const { verifier, challenge } = await this.cryptoProvider.generatePkceCodes();
// Set generated PKCE codes and method as session vars
req.session.pkceCodes = {
challengeMethod: 'S256',
verifier: verifier,
challenge: challenge,
};
/**
* By manipulating the request objects below before each request, we can obtain
* auth artifacts with desired claims. For more information, visit:
* https://azuread.github.io/microsoft-authentication-library-for-js/ref/modules/_azure_msal_node.html#authorizationurlrequest
* https://azuread.github.io/microsoft-authentication-library-for-js/ref/modules/_azure_msal_node.html#authorizationcoderequest
**/
req.session.authCodeUrlRequest = {
...authCodeUrlRequestParams,
responseMode: msal.ResponseMode.FORM_POST, // recommended for confidential clients
codeChallenge: req.session.pkceCodes.challenge,
codeChallengeMethod: req.session.pkceCodes.challengeMethod,
};
req.session.authCodeRequest = {
...authCodeRequestParams,
code: '',
};
try {
const authCodeUrlResponse = await msalInstance.getAuthCodeUrl(req.session.authCodeUrlRequest);
res.redirect(authCodeUrlResponse);
} catch (error) {
next(error);
}
};
}
/**
* Retrieves cloud discovery metadata from the /discovery/instance endpoint
* @returns
*/
async getCloudDiscoveryMetadata(authority) {
const endpoint = 'https://login.microsoftonline.com/common/discovery/instance';
try {
const response = await axios.get(endpoint, {
params: {
'api-version': '1.1',
'authorization_endpoint': `${authority}/oauth2/v2.0/authorize`
}
});
return await response.data;
} catch (error) {
throw error;
}
}
When the user selects the Sign in link, they're brought to the Microsoft identity platform authorization endpoint.
A successful sign-in redirects the user to the auth_response
route, which completes the sign-in process using auth.complete_login
, renders errors if any, and redirects the now authenticated user to the home page.
@app.route(app_config.REDIRECT_PATH)
def auth_response():
result = auth.complete_log_in(request.args)
if "error" in result:
return render_template("auth_error.html", result=result)
return redirect(url_for("index"))
After the user has signed in to your app, you'll want to enable them to sign out.
Sign out
Signing out from a web app involves more than removing the information about the signed-in account from the web app's state.
The web app must also redirect the user to the Microsoft identity platform logout
endpoint to sign out.
When your web app redirects the user to the logout
endpoint, this endpoint clears the user's session from the browser. If your app didn't go to the logout
endpoint, the user can reauthenticate to your app without entering their credentials again. The reason is that they'll have a valid single sign-in session with the Microsoft identity platform.
To learn more, see the Send a sign out request section in the Microsoft identity platform and the OpenID Connect protocol documentation.
Application registration
During the application registration, you register a front-channel logout URL. In our tutorial, you registered https://localhost:44321/signout-oidc
in the Front-channel logout URL field on the Authentication page. For details, see Register the webApp app.
During the application registration, you don't need to register an extra front-channel logout URL. The app is called back on its main URL.
No front-channel logout URL is required in the application registration.
No front-channel logout URL is required in the application registration.
During the application registration, you don't need to register an extra front-channel logout URL. The app is called back on its main URL.
In ASP.NET, selecting the Sign out button in the web app triggers the SignOut
action on the AccountController
controller (see below)
<ul class="navbar-nav">
@if (User.Identity.IsAuthenticated)
{
<li class="nav-item">
<span class="navbar-text text-dark">Hello @User.Identity.Name!</span>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignOut">Sign out</a>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignIn">Sign in</a>
</li>
}
</ul>
In ASP.NET MVC, the sign out button is exposed in Views\Shared\_LoginPartial.cshtml
. It's displayed only when there's an authenticated account. That is, it's displayed when the user has previously signed in.
@if (Request.IsAuthenticated)
{
<text>
<ul class="nav navbar-nav navbar-right">
<li class="navbar-text">
Hello, @User.Identity.Name!
</li>
<li>
@Html.ActionLink("Sign out", "SignOut", "Account")
</li>
</ul>
</text>
}
else
{
<ul class="nav navbar-nav navbar-right">
<li>@Html.ActionLink("Sign in", "SignIn", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })</li>
</ul>
}
In our Java quickstart, the sign out button is located in the main/resources/templates/auth_page.html file.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<body>
<form action="/msal4jsample/sign_out">
<input type="submit" value="Sign out">
</form>
...
{{#if isAuthenticated }}
<a href="/auth/signout">Sign out</a>
In the Python quickstart, the sign out button is located in the templates/index.html file.
<li><a href="/logout">Logout</a></li>
SignOut
action of the controller
In previous versions of the ASP.NET Core templates, the Account
controller was embedded with the web app. That's no longer the case because the controller is now part of the Microsoft.Identity.Web.UI NuGet package. See AccountController.cs for details.
Sets an OpenID redirect URI to /Account/SignedOut
so that the controller is called back when Microsoft Entra ID has completed the sign out.
Calls Signout()
, which lets the OpenID Connect middleware contact the Microsoft identity platform logout
endpoint. The endpoint then:
- Clears the session cookie from the browser.
- Calls back the post-logout redirect URI. By default, the post-logout redirect URI displays the signed-out view page SignedOut.cshtml.cs. This page is also provided as part of Microsoft.Identity.Web.
In ASP.NET, signing out is triggered from the SignOut()
method on a controller (for instance, AccountController.cs#L25-L31). This method isn't part of the .NET Framework, contrary to what happens in ASP.NET Core. It:
- Sends an OpenID sign out challenge.
- Clears the cache.
- Redirects to the page that it wants.
/// <summary>
/// Send an OpenID Connect sign-out request.
/// </summary>
public void SignOut()
{
HttpContext.GetOwinContext()
.Authentication
.SignOut(CookieAuthenticationDefaults.AuthenticationType);
Response.Redirect("/");
}
In Java, sign out is handled by calling the Microsoft identity platform logout
endpoint directly and providing the post_logout_redirect_uri
value. For details, see AuthPageController.java#L50-L60.
@RequestMapping("/msal4jsample/sign_out")
public void signOut(HttpServletRequest httpRequest, HttpServletResponse response) throws IOException {
httpRequest.getSession().invalidate();
String endSessionEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/logout";
String redirectUrl = "http://localhost:8080/msal4jsample/";
response.sendRedirect(endSessionEndpoint + "?post_logout_redirect_uri=" +
URLEncoder.encode(redirectUrl, "UTF-8"));
}
When the user selects the Sign out button, the app triggers the /auth/signout
route, which destroys the session and redirects the browser to Microsoft identity platform sign out endpoint.
logout(options = {}) {
return (req, res, next) => {
/**
* Construct a logout URI and redirect the user to end the
* session with Azure AD. For more information, visit:
* https://docs.microsoft.com/azure/active-directory/develop/v2-protocols-oidc#send-a-sign-out-request
*/
let logoutUri = `${this.msalConfig.auth.authority}/oauth2/v2.0/`;
if (options.postLogoutRedirectUri) {
logoutUri += `logout?post_logout_redirect_uri=${options.postLogoutRedirectUri}`;
}
req.session.destroy(() => {
res.redirect(logoutUri);
});
}
}
When the user selects Logout, the app triggers the logout
route, which redirects the browser to the Microsoft identity platform sign out endpoint.
@app.route("/logout")
def logout():
return redirect(auth.log_out(url_for("index", _external=True)))
Intercepting the call to the logout
endpoint
The post-logout URI enables applications to participate in the global sign out.
The ASP.NET Core OpenID Connect middleware enables your app to intercept the call to the Microsoft identity platform logout
endpoint by providing an OpenID Connect event named OnRedirectToIdentityProviderForSignOut
. This is handled automatically by Microsoft.Identity.Web (which clears accounts in the case where your web app calls web APIs).
In ASP.NET, you delegate to the middleware to execute the sign out, clearing the session cookie:
public class AccountController : Controller
{
...
public void EndSession()
{
Request.GetOwinContext().Authentication.SignOut();
Request.GetOwinContext().Authentication.SignOut(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ApplicationCookie);
this.HttpContext.GetOwinContext().Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
}
}
In the Java quickstart, the post-logout redirect URI just displays the index.html page.
In the Node quickstart, the post-logout redirect URI is used to redirect the browser back to sample home page after the user completes the sign out process with the Microsoft identity platform.
In the Python quickstart, the post-logout redirect URI just displays the index.html page.
Protocol
If you want to learn more about sign out, read the protocol documentation that's available from OpenID Connect.
Next steps