適用対象:
従業員テナント
外部テナント (詳細はこちら)
ユーザーをサインインさせる Web アプリのコードにサインインを追加する方法について学習します。 次に、ユーザーをサインアウトさせる方法について学習します。
サインイン
サインインは、次の 2 つの部分から構成されます。
- HTML ページのサインイン ボタン
- コントローラーの分離コードでのサインイン アクション
ASP.NET Core では、Microsoft ID プラットフォーム アプリケーションの場合、 [サインイン] ボタンは Views\Shared\_LoginPartial.cshtml
(MVC アプリの場合) または Pages\Shared\_LoginPartial.cshtm
(Razor アプリの場合) 上に表示されます。 ユーザーが認証済みでない場合にのみ、表示されます。 つまり、ユーザーがまだサインインしていない場合やサインアウト済みの場合に表示されます。反対に、ユーザーが既にサインインしている場合は、 [サインアウト] ボタンが表示されます。 アカウント コントローラーは、Microsoft.Identity.Web.UI NuGet パッケージの 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>
ASP.NET MVC では、[サインイン] ボタンは Views\Shared\_LoginPartial.cshtml
にあります。 ユーザーが認証済みでない場合にのみ、表示されます。 つまり、ユーザーがまだサインインしていない場合やサインアウト済みの場合に表示されます。
@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>
}
Java のクイックスタートでは、サインイン ボタンは main/resources/templates/index.html ファイルに配置されています。
<!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>
Node.js クイックスタートで、サインイン ボタンのコードは index.hbs テンプレート ファイルにあります。
<p>Welcome to {{title}}</p>
<a href="/auth/signin">Sign in</a>
このテンプレートは、アプリのメイン (インデックス) ルートを介して提供されます。
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,
});
});
Python のクイックスタートでは、サインイン リンクのコードはテンプレート ファイル login.html にあります。
<ul><li><a href='{{ auth_uri }}'>Sign In</a></li></ul>
認証されていないユーザーがホーム ページにアクセスすると、index
内の ルートによってユーザーが login
ルートにリダイレクトされます。
@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__)
login
ルートによって適切な auth_uri
が判別され、login.html テンプレートがレンダリングされます。
@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
アクション
ASP.NET では、Web アプリの [サインイン] ボタンを選択すると、SignIn
コントローラーの AccountController
アクションがトリガーされます。 以前のバージョンの ASP.NET Core テンプレートでは、Account
コントローラーは Web アプリに埋め込まれていました。 現在では、コントローラーは Microsoft.Identity.Web.UI NuGet パッケージの一部になったため、これは当てはまらなくなりました。 詳細については、AccountController.cs を参照してください。
このコントローラーでは、Azure AD B2C アプリケーションも処理されます。
ASP.NET では、サインインはコントローラー (例: SignIn()
) の メソッドからトリガーされます。 このメソッドは、(ASP.NET Core での動作とは対照的に) .NET Framework の一部ではありません。 リダイレクト URI を提案した後に OpenID サインイン チャレンジを送信します。
public void SignIn()
{
// Send an OpenID Connect sign-in request.
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" }, OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
Java では、サインアウトは、Microsoft ID プラットフォーム logout
エンドポイントを直接呼び出し、 post_logout_redirect_uri
値を指定することによって処理されます。 詳細については、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
ユーザーが [サインイン] リンクを/auth/signin
選択するとルートがトリガーされ、サインイン コントローラーが引き継ぎて、Microsoft ID プラットフォームでユーザーを認証します。
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;
}
}
ユーザーが [サインイン] リンクを選択すると、Microsoft ID プラットフォームの承認エンドポイントに移動します。
サインインに成功すると、ユーザーは auth_response
ルートにリダイレクトされ、auth.complete_login
を使用してサインイン プロセスが完了し、エラーがある場合はそれがレンダリングされ、この時点で認証されたユーザーがホーム ページにリダイレクトされます。
@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"))
ユーザーがアプリにサインインした後、ユーザーがサインアウトできるようにすることが必要です。
サインアウト
Web アプリからのサインアウトに必要なのは、サインインしたアカウントに関する情報を Web アプリの状態から削除することだけではありません。
サインアウトするには、Web アプリによってユーザーが Microsoft ID プラットフォーム logout
エンドポイントにリダイレクトされる必要もあります。
Web アプリによってユーザーが logout
エンドポイントにリダイレクトされると、このエンドポイントでは、ユーザーのセッションがブラウザーから消去されます。 アプリが logout
エンドポイントに移動しなかった場合、ユーザーは資格情報を再入力せずにアプリに再認証できます。 理由は、Microsoft ID プラットフォームとの有効なシングル サインイン セッションがあるからです。
詳細については、Microsoft ID プラットフォームの サインアウト要求の送信 に関するセクション と OpenID Connect プロトコルのドキュメントを 参照してください。
アプリケーションの登録
アプリケーションの登録時に、フロントチャネル ログアウト URL を登録します。 このチュートリアルでは、 https://localhost:44321/signout-oidc
ページの [Front-channel logout URL](フロントチャネル ログアウト URL) フィールドに と登録しました。 詳細については、「webApp アプリを登録する」を参照してください。
アプリケーションの登録時に、追加のフロントチャネル ログアウト URL を登録する必要はありません。 アプリはメイン URL にコールバックされます。
アプリケーションの登録で、フロントチャネル ログアウト URL は必要ありません。
アプリケーションの登録で、フロントチャネル ログアウト URL は必要ありません。
アプリケーションの登録時に、追加のフロントチャネル ログアウト URL を登録する必要はありません。 アプリはメイン URL にコールバックされます。
ASP.NET では、Web アプリ上の [サインアウト] ボタンを選択すると、SignOut
コントローラー上の AccountController
アクションがトリガーされます (上記を参照)
<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>
ASP.NET MVC では、サインアウト ボタンは Views\Shared\_LoginPartial.cshtml
で公開されます。 認証済みのアカウントがある場合にのみ表示されます。 つまり、ユーザーが既にサインインしている場合に表示されます。
@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>
}
Java クイック スタートでは、サインアウト ボタンは main/resources/templates/auth_page.html ファイルにあります。
<!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>
Python クイック スタートでは、サインアウト ボタンは テンプレート/index.html ファイルにあります。
<li><a href="/logout">Logout</a></li>
コントローラーの SignOut
アクション
以前のバージョンの ASP.NET Core テンプレートでは、Account
コントローラーは Web アプリに埋め込まれていました。 現在では、コントローラーは Microsoft.Identity.Web.UI NuGet パッケージの一部になったため、これは当てはまらなくなりました。 詳細については、AccountController.cs を参照してください。
Microsoft Entra ID がサインアウトを完了したときにコントローラーが呼び出されるように、OpenID リダイレクト URI を /Account/SignedOut
に設定します。
OpenID Connect ミドルウェアで Microsoft ID プラットフォームの Signout()
エンドポイントに連絡できるようにする logout
を呼び出します。 その後、エンドポイントは次のことを行います。
- ブラウザーからセッション Cookie を消去します。
- ログアウト後のリダイレクト URI をコールバックします。 既定では、ログアウト後のリダイレクト URI によって、サインアウト済みビューのページ SignedOut.cshtml.cs が表示されます。 このページは、Microsoft.Identity.Web の一部としても提供されています。
ASP.NET では、サインアウトは、コントローラー (例: SignOut()
) の メソッドからトリガーされます。 このメソッドは、ASP.NET Core での動作とは対照的に、.NET Framework の一部ではありません。 それでは次のことが行われます。
- OpenID サインアウト チャレンジを送信します。
- キャッシュをクリアします。
- 目的のページにリダイレクトします。
/// <summary>
/// Send an OpenID Connect sign-out request.
/// </summary>
public void SignOut()
{
HttpContext.GetOwinContext()
.Authentication
.SignOut(CookieAuthenticationDefaults.AuthenticationType);
Response.Redirect("/");
}
Java では、サインアウトは、Microsoft ID プラットフォーム logout
エンドポイントを直接呼び出し、 post_logout_redirect_uri
値を指定することによって処理されます。 詳細については、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"));
}
ユーザーが [サインアウト ] ボタンを選択すると、アプリによって /auth/signout
ルートがトリガーされ、セッションが破棄され、ブラウザーが Microsoft ID プラットフォームサインアウト エンドポイントにリダイレクトされます。
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);
});
}
}
ユーザーが ログアウトを選択すると、アプリによって logout
ルートがトリガーされ、ブラウザーが Microsoft ID プラットフォームのサインアウト エンドポイントにリダイレクトされます。
@app.route("/logout")
def logout():
return redirect(auth.log_out(url_for("index", _external=True)))
logout
エンドポイントへの呼び出しをインターセプトする
ログアウト後 URI を使用すると、アプリケーションはグローバル サインアウトに参加できます。
ASP.NET Core OpenID Connect ミドルウェアでは、logout
という名前の OpenID Connect イベントを提供することで、お客様のアプリで Microsoft ID プラットフォーム OnRedirectToIdentityProviderForSignOut
エンドポイントへの呼び出しをインターセプトすることができます。 これは、Microsoft.Identity.Web によって自動的に処理されます (Web アプリが Web API を呼び出す場合にアカウントがクリアされます)。
ASP.NET では、ミドルウェアに委任してサインアウトを実行し、セッション 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);
}
}
Java のクイックスタートでは、ログアウト後のリダイレクト URI は index.html ページを表示するだけです。
Node クイック スタートでは、ユーザーが Microsoft ID プラットフォームを使用してサインアウト プロセスを完了した後、ログアウト後のリダイレクト URI を使用してブラウザーをサンプル ホーム ページにリダイレクトします。
Python のクイックスタートでは、ログアウト後のリダイレクト URI は index.html ページを表示するだけです。
プロトコル
サインアウトの詳細については、 OpenID Connect から入手できるプロトコルのドキュメントを参照してください。
次のステップ