적용:
직원 테넌트
외부 테넌트(자세히 알아보기)
사용자를 로그인하는 웹앱의 코드에 로그인을 추가하는 방법을 알아봅니다. 그런 다음, 그들이 로그아웃할 수 있도록 하는 방법을 알아봅니다.
로그인
로그인은 다음과 같은 두 부분으로 구성됩니다.
- 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에서 웹앱의 로그인 단추를 선택하면 SignIn
컨트롤러에서 AccountController
동작이 트리거됩니다. 이전 버전의 ASP.NET Core 템플릿에서는 Account
컨트롤러가 웹앱에 포함되어 있었습니다. 이제는 컨트롤러가 Microsoft.Identity.Web.UI NuGet 패키지의 일부이기 때문에 더 이상 포함되지 않습니다. 자세한 내용은 AccountController.cs를 참조하세요.
또한 이 컨트롤러는 Azure AD B2C 애플리케이션을 처리합니다.
ASP.NET에서는 로그인이 컨트롤러의 SignIn()
메서드(예: AccountController.cs#L16-L23)에서 트리거됩니다. 이 메서드는 .NET Framework의 일부가 아닙니다(ASP.NET Core의 경우와는 다름). 리디렉션 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"))
사용자가 앱에 로그인한 후 로그아웃하도록 설정하려고 합니다.
로그아웃
웹앱에서 로그아웃하는 것은 웹앱의 상태에서 로그인한 계정에 대한 정보를 제거하는 것 이상을 포함합니다.
또한 웹앱은 로그아웃할 사용자를 Microsoft ID 플랫폼 logout
엔드포인트로 리디렉션해야 합니다.
웹앱이 사용자를 logout
엔드포인트에 리디렉션하면 이 엔드포인트가 브라우저에서 사용자의 세션을 지웁니다. 앱이 logout
엔드포인트로 이동하지 않은 경우 사용자는 자격 증명을 다시 입력하지 않고도 앱에 다시 인증할 수 있습니다. 그 이유는 Microsoft ID 플랫폼을 사용하는 유효한 단일 로그인 세션이 있기 때문입니다.
자세한 내용은 Microsoft ID 플랫폼 및 OpenID Connect 프로토콜 설명서의 로그아웃 요청 섹션을 참조하세요.
애플리케이션 등록
애플리케이션을 등록하는 동안 전면 채널 로그아웃 URL을 등록합니다. 이 자습서에서는 https://localhost:44321/signout-oidc
페이지의 전면 채널 로그아웃 URL 필드에 를 등록했습니다. 자세한 내용은 WebApp 앱 등록을 참조하세요.
애플리케이션을 등록하는 동안 추가 전면 채널 로그아웃 URL을 등록할 필요가 없습니다. 앱은 기본 URL에서 다시 호출됩니다.
애플리케이션 등록에는 전면 채널 로그아웃 URL이 필요하지 않습니다.
애플리케이션 등록에는 전면 채널 로그아웃 URL이 필요하지 않습니다.
애플리케이션을 등록하는 동안 추가 전면 채널 로그아웃 URL을 등록할 필요가 없습니다. 앱은 기본 URL에서 다시 호출됩니다.
ASP.NET에서 웹앱의 로그아웃 단추를 선택하면 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 빠른 시작에서 로그아웃 단추는 기본/리소스/템플릿/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
컨트롤러가 웹앱에 포함되어 있었습니다. 이제는 컨트롤러가 Microsoft.Identity.Web.UI NuGet 패키지의 일부이기 때문에 더 이상 포함되지 않습니다. 자세한 내용은 AccountController.cs를 참조하세요.
ASP.NET에서 컨트롤러의 SignOut()
메서드(예: AccountController.cs#L25-L31)로부터 로그아웃이 트리거됩니다. 이 메서드는 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에서 자동으로 처리됩니다(웹앱이 웹 API를 호출하는 경우 계정을 지웁니다).
ASP.NET 세션 쿠키를 지우고 로그아웃을 실행하도록 미들웨어에 위임합니다.
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 빠른 시작에서 로그아웃 후 리디렉션 URI는 사용자가 Microsoft ID 플랫폼으로 로그아웃 프로세스를 완료한 후 브라우저를 샘플 홈페이지로 다시 리디렉션하는 데 사용됩니다.
Python 빠른 시작에서는 사후 로그아웃 리디렉션 URI가 index.html 페이지만 표시합니다.
프로토콜
로그아웃에 대해 자세히 알아보려면 OpenID Connect사용할 수 있는 프로토콜 설명서를 읽어보세요.
다음 단계