Azure B2C - 使用 msal 获取调用 Graph 的授权 API
Azure B2C - Use msal to get authorization to call Graph API
我最近开始使用 B2C。我设法让他们 sample application/API 使用 MSAL 和 API 与我自己的租户一起工作。
现在我想:
- 弄清楚如何在不使用 API 的情况下 运行 这个示例。该示例使用 Scopes 获得对 API 的 read/write 访问权限。如果我从应用程序中删除对 API 的引用,它就不再有效。当然应该有一些方法可以在不需要 API 的情况下对 B2C 进行身份验证?这对我的应用程序来说并不是很重要,但我很好奇 Web 服务 HAS 是否作为身份验证过程的一部分存在?
- 与 Graph Api(Windows 或 Microsoft Graph?)通信。 sample MS 提供使用 ADAL 和一些控制台应用程序。我找不到使用 MSAL 的示例,因此我无法将它合并到我自己的应用程序中。现在可以使用 MSAL 调用 Graph API 了吗?如果是,是否有一些关于如何在某处执行此操作的文档?
我尝试简单地按照上面的文档并注册一个 app/granting 它权限。然后将客户端 ID/key 放入我自己的应用程序(第一个示例中的 MSAL 应用程序),但随后我只收到一条来自 B2C 的消息,如下所示:
Correlation ID: 01040e7b-846c-4f81-9a0f-ff515fd00398
Timestamp: 2018-01-30 10:55:37Z
AADB2C90068: The provided application with ID '9cd938c6-d3ed-4146-aee5-a661cd7d984b' is not valid against this service. Please use an application created via the B2C portal and try again.
它确实没有通过 B2C 门户注册,但说明书上是这么说的;在应用程序注册下的B2C 租户中注册它,而不是 B2c 门户。
创业公司 class 的神奇之处如下:
public partial class Startup
{
// App config settings
public static string ClientId = ConfigurationManager.AppSettings["ida:ClientId"];
public static string ClientSecret = ConfigurationManager.AppSettings["ida:ClientSecret"];
public static string AadInstance = ConfigurationManager.AppSettings["ida:AadInstance"];
public static string Tenant = ConfigurationManager.AppSettings["ida:Tenant"];
public static string RedirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
public static string ServiceUrl = ConfigurationManager.AppSettings["api:TaskServiceUrl"];
public static string ApiIdentifier = ConfigurationManager.AppSettings["api:ApiIdentifier"];
public static string ReadTasksScope = ApiIdentifier + ConfigurationManager.AppSettings["api:ReadScope"];
public static string WriteTasksScope = ApiIdentifier + ConfigurationManager.AppSettings["api:WriteScope"];
public static string[] Scopes = new string[] { ReadTasksScope, WriteTasksScope };
// B2C policy identifiers
public static string SignUpSignInPolicyId = ConfigurationManager.AppSettings["ida:SignUpSignInPolicyId"];
public static string EditProfilePolicyId = ConfigurationManager.AppSettings["ida:EditProfilePolicyId"];
public static string ResetPasswordPolicyId = ConfigurationManager.AppSettings["ida:ResetPasswordPolicyId"];
public static string DefaultPolicy = SignUpSignInPolicyId;
// OWIN auth middleware constants
public const string ObjectIdElement = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
// Authorities
public static string Authority = String.Format(AadInstance, Tenant, DefaultPolicy);
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Generate the metadata address using the tenant and policy information
MetadataAddress = String.Format(AadInstance, Tenant, DefaultPolicy),
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = ClientId,
RedirectUri = RedirectUri,
PostLogoutRedirectUri = RedirectUri,
// Specify the callbacks for each type of notifications
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
},
// Specify the claims to validate
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
},
// Specify the scope by appending all of the scopes requested into one string (seperated by a blank space)
Scope = $"{OpenIdConnectScopes.OpenId} {ReadTasksScope} {WriteTasksScope}"
}
);
}
/*
* On each call to Azure AD B2C, check if a policy (e.g. the profile edit or password reset policy) has been specified in the OWIN context.
* If so, use that policy when making the call. Also, don't request a code (since it won't be needed).
*/
private Task OnRedirectToIdentityProvider(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
var policy = notification.OwinContext.Get<string>("Policy");
if (!string.IsNullOrEmpty(policy) && !policy.Equals(DefaultPolicy))
{
notification.ProtocolMessage.Scope = OpenIdConnectScopes.OpenId;
notification.ProtocolMessage.ResponseType = OpenIdConnectResponseTypes.IdToken;
notification.ProtocolMessage.IssuerAddress = notification.ProtocolMessage.IssuerAddress.ToLower().Replace(DefaultPolicy.ToLower(), policy.ToLower());
}
return Task.FromResult(0);
}
/*
* Catch any failures received by the authentication middleware and handle appropriately
*/
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
notification.HandleResponse();
// Handle the error code that Azure AD B2C throws when trying to reset a password from the login page
// because password reset is not supported by a "sign-up or sign-in policy"
if (notification.ProtocolMessage.ErrorDescription != null && notification.ProtocolMessage.ErrorDescription.Contains("AADB2C90118"))
{
// If the user clicked the reset password link, redirect to the reset password route
notification.Response.Redirect("/Account/ResetPassword");
}
else if (notification.Exception.Message == "access_denied")
{
notification.Response.Redirect("/");
}
else
{
notification.Response.Redirect("/Home/Error?message=" + notification.Exception.Message);
}
return Task.FromResult(0);
}
/*
* Callback function when an authorization code is received
*/
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
// Extract the code from the response notification
var code = notification.Code;
string signedInUserID = notification.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
TokenCache userTokenCache = new MSALSessionCache(signedInUserID, notification.OwinContext.Environment["System.Web.HttpContextBase"] as HttpContextBase).GetMsalCacheInstance();
ConfidentialClientApplication cca = new ConfidentialClientApplication(ClientId, Authority, RedirectUri, new ClientCredential(ClientSecret), userTokenCache, null);
try
{
AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(code, Scopes);
}
catch (Exception ex)
{
//TODO: Handle
throw;
}
}
}
这个答案只是针对问题 1,我不确定问题 2,但据我所知没有公开记录。
B2C 不要求您在构建应用程序时使用 API/service。在您正在查看的示例中,它使用 2 个库来做略有不同(但相关)的事情。
首先,它使用 OWIN 中间件模块来帮助简化身份验证部分。这有助于您的应用识别用户是谁,但不会授权您的应用代表他们执行操作或访问数据。该库将为您提供与最终用户的会话和有关他们的基本信息以及您稍后可以使用的授权代码。
正在使用的另一个库是 MSAL。 MSAL 是用于令牌获取和管理的客户端库。在这种情况下,在使用上述中间件进行初始身份验证后,MSAL will use the authorization code to get access and refresh tokens that your app can use to call APIs. This is able to take place without end user interaction because they would have already consented to the app (and you would've configured the permissions your app needed). MSAL then manages the refresh and caching of these tokens, and makes them accessible via AcquireTokenSilent()。
为了从示例应用程序中删除 API 功能,您需要做的不仅仅是删除范围。具体来说,消除 TaskController.cs that is trying to call APIs, remove most usage of MSAL, and likely a few more things. This sample 中的代码可实现纯 Web 应用程序架构(警告:它适用于 Azure AD 而不是 Azure AD B2C。代码非常相似,但需要进行一些修改)。
关于 #2,您只能将 Azure AD Graph API 与 Azure AD B2C 目录一起使用,如 the "Azure AD B2C: Use the Azure AD Graph API" article 中所述。
这是方法(我从 复制而来)...
Azure AD B2C 使用 Azure AD v2.0 终结点颁发令牌:
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
Azure AD Graph API 需要使用 Azure AD v1.0 端点颁发的令牌:
https://login.microsoftonline.com/{tenant}/oauth2/token
在设计时:
- 使用 Azure AD B2C 门户注册 Web 应用程序。
- Register the web application using the Azure AD portal and grant the Read directory data permission.
运行时:
- Web 应用程序将最终用户重定向到 Azure AD B2C v2.0 终结点以进行登录。 Azure AD B2C 颁发包含用户标识符的 ID 令牌。
- Web 应用程序使用在设计时在步骤 2 中创建的应用程序凭据从 Azure AD v1.0 端点获取访问令牌。
- Web 应用程序调用 Azure AD Graph API,传递在步骤 1 中收到的用户标识符,以及在步骤 2 中颁发的访问令牌,并查询用户对象等。
我最近开始使用 B2C。我设法让他们 sample application/API 使用 MSAL 和 API 与我自己的租户一起工作。
现在我想:
- 弄清楚如何在不使用 API 的情况下 运行 这个示例。该示例使用 Scopes 获得对 API 的 read/write 访问权限。如果我从应用程序中删除对 API 的引用,它就不再有效。当然应该有一些方法可以在不需要 API 的情况下对 B2C 进行身份验证?这对我的应用程序来说并不是很重要,但我很好奇 Web 服务 HAS 是否作为身份验证过程的一部分存在?
- 与 Graph Api(Windows 或 Microsoft Graph?)通信。 sample MS 提供使用 ADAL 和一些控制台应用程序。我找不到使用 MSAL 的示例,因此我无法将它合并到我自己的应用程序中。现在可以使用 MSAL 调用 Graph API 了吗?如果是,是否有一些关于如何在某处执行此操作的文档?
我尝试简单地按照上面的文档并注册一个 app/granting 它权限。然后将客户端 ID/key 放入我自己的应用程序(第一个示例中的 MSAL 应用程序),但随后我只收到一条来自 B2C 的消息,如下所示:
Correlation ID: 01040e7b-846c-4f81-9a0f-ff515fd00398 Timestamp: 2018-01-30 10:55:37Z AADB2C90068: The provided application with ID '9cd938c6-d3ed-4146-aee5-a661cd7d984b' is not valid against this service. Please use an application created via the B2C portal and try again.
它确实没有通过 B2C 门户注册,但说明书上是这么说的;在应用程序注册下的B2C 租户中注册它,而不是 B2c 门户。
创业公司 class 的神奇之处如下:
public partial class Startup
{
// App config settings
public static string ClientId = ConfigurationManager.AppSettings["ida:ClientId"];
public static string ClientSecret = ConfigurationManager.AppSettings["ida:ClientSecret"];
public static string AadInstance = ConfigurationManager.AppSettings["ida:AadInstance"];
public static string Tenant = ConfigurationManager.AppSettings["ida:Tenant"];
public static string RedirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
public static string ServiceUrl = ConfigurationManager.AppSettings["api:TaskServiceUrl"];
public static string ApiIdentifier = ConfigurationManager.AppSettings["api:ApiIdentifier"];
public static string ReadTasksScope = ApiIdentifier + ConfigurationManager.AppSettings["api:ReadScope"];
public static string WriteTasksScope = ApiIdentifier + ConfigurationManager.AppSettings["api:WriteScope"];
public static string[] Scopes = new string[] { ReadTasksScope, WriteTasksScope };
// B2C policy identifiers
public static string SignUpSignInPolicyId = ConfigurationManager.AppSettings["ida:SignUpSignInPolicyId"];
public static string EditProfilePolicyId = ConfigurationManager.AppSettings["ida:EditProfilePolicyId"];
public static string ResetPasswordPolicyId = ConfigurationManager.AppSettings["ida:ResetPasswordPolicyId"];
public static string DefaultPolicy = SignUpSignInPolicyId;
// OWIN auth middleware constants
public const string ObjectIdElement = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
// Authorities
public static string Authority = String.Format(AadInstance, Tenant, DefaultPolicy);
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Generate the metadata address using the tenant and policy information
MetadataAddress = String.Format(AadInstance, Tenant, DefaultPolicy),
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = ClientId,
RedirectUri = RedirectUri,
PostLogoutRedirectUri = RedirectUri,
// Specify the callbacks for each type of notifications
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
},
// Specify the claims to validate
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
},
// Specify the scope by appending all of the scopes requested into one string (seperated by a blank space)
Scope = $"{OpenIdConnectScopes.OpenId} {ReadTasksScope} {WriteTasksScope}"
}
);
}
/*
* On each call to Azure AD B2C, check if a policy (e.g. the profile edit or password reset policy) has been specified in the OWIN context.
* If so, use that policy when making the call. Also, don't request a code (since it won't be needed).
*/
private Task OnRedirectToIdentityProvider(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
var policy = notification.OwinContext.Get<string>("Policy");
if (!string.IsNullOrEmpty(policy) && !policy.Equals(DefaultPolicy))
{
notification.ProtocolMessage.Scope = OpenIdConnectScopes.OpenId;
notification.ProtocolMessage.ResponseType = OpenIdConnectResponseTypes.IdToken;
notification.ProtocolMessage.IssuerAddress = notification.ProtocolMessage.IssuerAddress.ToLower().Replace(DefaultPolicy.ToLower(), policy.ToLower());
}
return Task.FromResult(0);
}
/*
* Catch any failures received by the authentication middleware and handle appropriately
*/
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
notification.HandleResponse();
// Handle the error code that Azure AD B2C throws when trying to reset a password from the login page
// because password reset is not supported by a "sign-up or sign-in policy"
if (notification.ProtocolMessage.ErrorDescription != null && notification.ProtocolMessage.ErrorDescription.Contains("AADB2C90118"))
{
// If the user clicked the reset password link, redirect to the reset password route
notification.Response.Redirect("/Account/ResetPassword");
}
else if (notification.Exception.Message == "access_denied")
{
notification.Response.Redirect("/");
}
else
{
notification.Response.Redirect("/Home/Error?message=" + notification.Exception.Message);
}
return Task.FromResult(0);
}
/*
* Callback function when an authorization code is received
*/
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
// Extract the code from the response notification
var code = notification.Code;
string signedInUserID = notification.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
TokenCache userTokenCache = new MSALSessionCache(signedInUserID, notification.OwinContext.Environment["System.Web.HttpContextBase"] as HttpContextBase).GetMsalCacheInstance();
ConfidentialClientApplication cca = new ConfidentialClientApplication(ClientId, Authority, RedirectUri, new ClientCredential(ClientSecret), userTokenCache, null);
try
{
AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(code, Scopes);
}
catch (Exception ex)
{
//TODO: Handle
throw;
}
}
}
这个答案只是针对问题 1,我不确定问题 2,但据我所知没有公开记录。
B2C 不要求您在构建应用程序时使用 API/service。在您正在查看的示例中,它使用 2 个库来做略有不同(但相关)的事情。
首先,它使用 OWIN 中间件模块来帮助简化身份验证部分。这有助于您的应用识别用户是谁,但不会授权您的应用代表他们执行操作或访问数据。该库将为您提供与最终用户的会话和有关他们的基本信息以及您稍后可以使用的授权代码。
正在使用的另一个库是 MSAL。 MSAL 是用于令牌获取和管理的客户端库。在这种情况下,在使用上述中间件进行初始身份验证后,MSAL will use the authorization code to get access and refresh tokens that your app can use to call APIs. This is able to take place without end user interaction because they would have already consented to the app (and you would've configured the permissions your app needed). MSAL then manages the refresh and caching of these tokens, and makes them accessible via AcquireTokenSilent()。
为了从示例应用程序中删除 API 功能,您需要做的不仅仅是删除范围。具体来说,消除 TaskController.cs that is trying to call APIs, remove most usage of MSAL, and likely a few more things. This sample 中的代码可实现纯 Web 应用程序架构(警告:它适用于 Azure AD 而不是 Azure AD B2C。代码非常相似,但需要进行一些修改)。
关于 #2,您只能将 Azure AD Graph API 与 Azure AD B2C 目录一起使用,如 the "Azure AD B2C: Use the Azure AD Graph API" article 中所述。
这是方法(我从
Azure AD B2C 使用 Azure AD v2.0 终结点颁发令牌:
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
Azure AD Graph API 需要使用 Azure AD v1.0 端点颁发的令牌:
https://login.microsoftonline.com/{tenant}/oauth2/token
在设计时:
- 使用 Azure AD B2C 门户注册 Web 应用程序。
- Register the web application using the Azure AD portal and grant the Read directory data permission.
运行时:
- Web 应用程序将最终用户重定向到 Azure AD B2C v2.0 终结点以进行登录。 Azure AD B2C 颁发包含用户标识符的 ID 令牌。
- Web 应用程序使用在设计时在步骤 2 中创建的应用程序凭据从 Azure AD v1.0 端点获取访问令牌。
- Web 应用程序调用 Azure AD Graph API,传递在步骤 1 中收到的用户标识符,以及在步骤 2 中颁发的访问令牌,并查询用户对象等。