ASP.NET Core에서 Microsoft ID 플랫폼 애플리케이션의 경우 로그인 단추가 Views\Shared\_LoginPartial.cshtml(MVC 앱) 또는 Pages\Shared\_LoginPartial.cshtm(Razor 앱)에 표시됩니다. 사용자가 인증되지 않은 경우에만 표시됩니다. 즉, 사용자가 아직 로그인하지 않았거나 로그아웃했을 때 표시됩니다. 반대로 사용자가 이미 로그인한 경우 로그아웃 단추가 표시됩니다. 계정 컨트롤러는 MicrosoftIdentity라는 영역에 있는 Microsoft.Identity.Web.UI NuGet 패키지에 정의되어 있습니다.
인증되지 않은 사용자가 홈페이지를 방문하면 app.py의 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
))
ASP.NET에서 웹앱의 로그인 단추를 선택하면 AccountController 컨트롤러에서 SignIn 동작이 트리거됩니다. 이전 버전의 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 플랫폼을 사용하는 유효한 단일 로그인 세션이 있기 때문입니다.
이전 버전의 ASP.NET Core 템플릿에서는 Account 컨트롤러가 웹앱에 포함되어 있었습니다. 이제는 컨트롤러가 Microsoft.Identity.Web.UI NuGet 패키지의 일부이기 때문에 더 이상 포함되지 않습니다. 자세한 내용은 AccountController.cs를 참조하세요.
Microsoft Entra ID가 로그아웃을 완료하면 컨트롤러가 다시 호출되도록 OpenID 리디렉션 URI를 /Account/SignedOut으로 설정합니다.
Signout()을 호출합니다. 이는 OpenID Connect 미들웨어가 Microsoft ID 플랫폼 logout 엔드포인트에 연결할 수 있도록 합니다. 그런 다음 엔드포인트는 다음을 수행합니다.
브라우저에서 세션 쿠키를 지웁니다.
사후 로그아웃 리디렉션 URI를 다시 호출합니다. 기본적으로 사후 로그아웃 리디렉션 URI는 로그아웃된 보기 페이지인 SignedOut.cshtml.cs를 표시합니다. 이 페이지는 Microsoft.Identity.Web의 일부로도 제공됩니다.
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을 참조하세요.
사용자가 로그아웃 단추를 선택하면 앱이 세션을 삭제하고 브라우저를 Microsoft ID 플랫폼 로그아웃 엔드포인트로 리디렉션하는 /auth/signout 경로를 트리거합니다.
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);
});
}
}
사용자가 로그아웃을 선택하면 앱은 브라우저를 Microsoft ID 플랫폼 로그아웃 엔드포인트로 리디렉션하는 logout 경로를 트리거합니다.
ASP.NET Core OpenID Connect 미들웨어를 사용하면 앱에서 OnRedirectToIdentityProviderForSignOut이라는 OpenID Connect 이벤트를 제공하여 Microsoft ID 플랫폼 logout 엔드포인트에 대한 호출을 가로챌 수 있습니다. 이는 Microsoft.Identity.Web에서 자동으로 처리됩니다(웹앱이 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 페이지만 표시합니다.
노드 빠른 시작에서 로그아웃 후 리디렉션 URI는 사용자가 Microsoft ID 플랫폼으로 로그아웃 프로세스를 완료한 후 브라우저를 샘플 홈페이지로 다시 리디렉션하는 데 사용됩니다.
Python 빠른 시작에서는 사후 로그아웃 리디렉션 URI가 index.html 페이지만 표시합니다.
프로토콜
로그아웃에 대해 자세히 알아보려면 OpenID Connect에서 사용할 수 있는 프로토콜 설명서를 읽습니다.
다음 단계
여러 파트로 구성된 다음 자습서 시리즈에서 사용자를 로그인하는 ASP.NET Core 웹앱을 빌드하여 자세히 알아보세요.