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. Note that the Account controller is defined in the Microsoft.Identity.Web.UI NuGet package, in the Area named MicrosoftIdentity
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.
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, there's no sign-in button. The code-behind automatically prompts the user for sign-in when it's reaching the root of the web app. See app.py#L14-L18.
@app.route("/")
def index():
if not session.get("user"):
return redirect(url_for("login"))
return render_template('index.html', user=session["user"])
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 ASP.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.
async function redirectToAuthCodeUrl(req, res, next, authCodeUrlRequestParams, authCodeRequestParams) {
// Generate PKCE Codes before starting the authorization flow
const { verifier, challenge } = await 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 = {
redirectUri: REDIRECT_URI,
responseMode: 'form_post', // recommended for confidential clients
codeChallenge: req.session.pkceCodes.challenge,
codeChallengeMethod: req.session.pkceCodes.challengeMethod,
...authCodeUrlRequestParams,
};
req.session.authCodeRequest = {
redirectUri: REDIRECT_URI,
code: "",
...authCodeRequestParams,
};
// Get url to sign user in and consent to scopes needed for application
try {
const authCodeUrlResponse = await msalInstance.getAuthCodeUrl(req.session.authCodeUrlRequest);
res.redirect(authCodeUrlResponse);
} catch (error) {
next(error);
}
};
router.get('/signin', async function (req, res, next) {
// create a GUID for crsf
req.session.csrfToken = cryptoProvider.createNewGuid();
/**
* The 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 = cryptoProvider.base64Encode(
JSON.stringify({
csrfToken: req.session.csrfToken,
redirectTo: '/'
})
);
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: [],
};
const authCodeRequestParams = {
/**
* 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: [],
};
// trigger the first leg of auth code flow
return redirectToAuthCodeUrl(req, res, next, authCodeUrlRequestParams, authCodeRequestParams)
});
router.post('/redirect', async function (req, res, next) {
if (req.body.state) {
const state = JSON.parse(cryptoProvider.base64Decode(req.body.state));
// check if csrfToken matches
if (state.csrfToken === req.session.csrfToken) {
req.session.authCodeRequest.code = req.body.code; // authZ code
req.session.authCodeRequest.codeVerifier = req.session.pkceCodes.verifier // PKCE Code Verifier
try {
const tokenResponse = await msalInstance.acquireTokenByCode(req.session.authCodeRequest);
req.session.accessToken = tokenResponse.accessToken;
req.session.idToken = tokenResponse.idToken;
req.session.account = tokenResponse.account;
req.session.isAuthenticated = true;
res.redirect(state.redirectTo);
} catch (error) {
next(error);
}
} else {
next(new Error('csrf token does not match'));
}
} else {
next(new Error('state is missing'));
}
});
Unlike other platforms, MSAL Python takes care of letting the user sign in from the login page. See app.py#L20-L28.
@app.route("/login")
def login():
session["state"] = str(uuid.uuid4())
auth_url = _build_msal_app().get_authorization_request_url(
app_config.SCOPE, # Technically we can use an empty list [] to just sign in
# Here we choose to also collect user consent up front
state=session["state"],
redirect_uri=url_for("authorized", _external=True))
return "<a href='%s'>Login with Microsoft Identity</a>" % auth_url
The _build_msal_app() method is defined in app.py#L81-L88 as follows:
def _load_cache():
cache = msal.SerializableTokenCache()
if session.get("token_cache"):
cache.deserialize(session["token_cache"])
return cache
def _save_cache(cache):
if cache.has_state_changed:
session["token_cache"] = cache.serialize()
def _build_msal_app(cache=None):
return msal.ConfidentialClientApplication(
app_config.CLIENT_ID, authority=app_config.AUTHORITY,
client_credential=app_config.CLIENT_SECRET, token_cache=cache)
def _get_token_from_cache(scope=None):
cache = _load_cache() # This web app maintains one cache per session
cca = _build_msal_app(cache)
accounts = cca.get_accounts()
if accounts: # So all accounts belong to the current signed-in user
result = cca.acquire_token_silent(scope, account=accounts[0])
_save_cache(cache)
return result
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 will 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.
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 will be 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 will be called back on its main URL.
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.
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 Azure AD 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 ASP.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.
When the user selects the Sign out button, the app triggers the /signout route, which destroys the session and redirects the browser to Microsoft identity platform sign-out endpoint.
router.get('/signout', function (req, res) {
/**
* 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
*/
const logoutUri = `${msalConfig.auth.authority}/oauth2/v2.0/logout?post_logout_redirect_uri=${POST_LOGOUT_REDIRECT_URI}`;
req.session.destroy(() => {
res.redirect(logoutUri);
});
});
@app.route("/logout")
def logout():
session.clear() # Wipe out the user and the token cache from the session
return redirect( # Also need to log out from the Microsoft Identity platform
"https://login.microsoftonline.com/common/oauth2/v2.0/logout"
"?post_logout_redirect_uri=" + 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 logout 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 Open ID Connect.