我有一个Web应用程序(MVC)和一个WebApi,它们都由ADB2C在同一个租户中保护。 Web应用程序想要使用简单的HttpClient调用WebApi。 这是带有最新Visaul Studio项目模板的.NET Core 2.1。
可以提出以下几点:
我可以使用B2C成功登录并注册网络应用。我正在使用新的.NET Core 2.1模板,其中B2C以最少的代码加入到项目中。
我也使用向导创建WebApi项目(这是WebAPI的结果)。然后,我可以成功使用Postman来测试在WebApi中签名,其中Postman在租户中注册为自己的网络应用程序。这被描述为here
我正在我的本地机器上测试所有这些,我还没有部署到azurewebsites.net
我可以说网络应用正在使用
services.AddAuthentication( AzureADB2CDefaults.AuthenticationScheme )
.AddAzureADB2C( options =>
{
Configuration.Bind( "AzureAdB2C", options );
} );
WebApi startup.cs正在使用持票令牌:
services.AddAuthentication(AzureADB2CDefaults.BearerAuthenticationScheme)
.AddAzureADB2CBearer(options =>
{
Configuration.Bind( "AzureAdB2C", options );
} );
所以现在我在我的网络应用程序中有一个控制器动作,想要调用WebApi项目中的ValuesController来获取虚拟值。
这是我不明白的,因为使用以下代码,我能够获得令牌:
var httpClient = new HttpClient
{
BaseAddress = new Uri( _configuration["WebApi:BaseUrl"] )
};
var clientId = _configuration["AzureAdB2C:ClientId"]; //the client ID for the web app (not web api!)
var clientSecret = _configuration["AzureAdB2C:ClientSecret"]; //the secret for the web app
var authority = _configuration["AzureAdB2C:Authority"]; //the instance name and the custom domain name for this tenant
var id = _configuration["WebApi:Id"]; //the complete Url including the suffix of the web api
var authContext = new AuthenticationContext( authority );
var credentials = new ClientCredential( clientId, clientSecret );
var authResult = await authContext.AcquireTokenAsync( id, credentials );
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue( "Bearer", authResult.AccessToken );
此时我有令牌。我可以使用http://jwt.ms解密令牌,但令牌不包含声明。不明白为什么会这样。
但是当我调用GetStringAsync()时,我在ValuesController的Get()操作中得到了401未授权的异常。
//this fails with a 401
var result = await _httpClient.GetStringAsync( "api/values" );
//HttpRequestException: Response status code does not indicate success: 401 (Unauthorized).
对于" id"在上面的代码中,我使用完整的WebApi URL,如Azure门户中的WebApi属性所示。
" API访问"我已授予Web应用程序访问门户网站中的web api应用程序的权限。
我错过了什么?我不知道Postman的工作原理(在ADB2C中注册为网络应用),这不是
答案 0 :(得分:1)
于2018年6月27日开始编辑
我已经在GitHub为ASP.NET Core 2.1 Web应用程序创建了一个代码示例,该示例使用Azure AD B2C的ASP.NET Core 2.1身份验证中间件针对Azure AD B2C对最终用户进行身份验证,并获取了使用MSAL.NET访问令牌,并使用此访问令牌访问Web API。
以下答案总结了已为代码示例实现的内容。
于2018年6月27日结束编辑
要使ASP.NET Core 2.1 Web应用程序获取用于API应用程序的访问令牌,则必须:
public class AzureADB2CWithApiOptions : AzureADB2COptions
{
public string ApiScopes { get; set; }
public string ApiUrl { get; set; }
public string Authority => $"{Instance}/{Domain}/{DefaultPolicy}/v2.0";
public string RedirectUri { get; set; }
}
{
"AllowedHosts": "*",
"AzureADB2C": {
"ApiScopes": "https://***.onmicrosoft.com/demoapi/demo.read",
"ApiUrl": "https://***.azurewebsites.net/hello",
"CallbackPath": "/signin-oidc",
"ClientId": "***",
"ClientSecret": "***",
"Domain": "***.onmicrosoft.com",
"EditProfilePolicyId": "b2c_1_edit_profile",
"Instance": "https://login.microsoftonline.com/tfp",
"RedirectUri": "https://localhost:44316/signin-oidc",
"ResetPasswordPolicyId": "b2c_1_reset",
"SignUpSignInPolicyId": "b2c_1_susi"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
}
}
IConfigureNamedOptions<OpenIdConnectOptions>
接口,该接口为Azure AD B2C身份验证中间件配置OpenID Connect身份验证选项:public class AzureADB2COpenIdConnectOptionsConfigurator : IConfigureNamedOptions<OpenIdConnectOptions>
{
private readonly AzureADB2CWithApiOptions _options;
public AzureADB2COpenIdConnectOptionsConfigurator(IOptions<AzureADB2CWithApiOptions> optionsAccessor)
{
_options = optionsAccessor.Value;
}
public void Configure(string name, OpenIdConnectOptions options)
{
options.Events.OnAuthorizationCodeReceived = WrapOpenIdConnectEvent(options.Events.OnAuthorizationCodeReceived, OnAuthorizationCodeReceived);
options.Events.OnRedirectToIdentityProvider = WrapOpenIdConnectEvent(options.Events.OnRedirectToIdentityProvider, OnRedirectToIdentityProvider);
}
public void Configure(OpenIdConnectOptions options)
{
Configure(Options.DefaultName, options);
}
private static Func<TContext, Task> WrapOpenIdConnectEvent<TContext>(Func<TContext, Task> baseEventHandler, Func<TContext, Task> thisEventHandler)
{
return new Func<TContext, Task>(async context =>
{
await baseEventHandler(context);
await thisEventHandler(context);
});
}
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedContext context)
{
var clientCredential = new ClientCredential(context.Options.ClientSecret);
var userId = context.Principal.FindFirst(ClaimTypes.NameIdentifier).Value;
var userTokenCache = new SessionTokenCache(context.HttpContext, userId);
var confidentialClientApplication = new ConfidentialClientApplication(
context.Options.ClientId,
context.Options.Authority,
$"{context.Request.Scheme}://{context.Request.Host}{context.Request.Path}",
clientCredential,
userTokenCache.GetInstance(),
null);
try
{
var authenticationResult = await confidentialClientApplication.AcquireTokenByAuthorizationCodeAsync(
context.ProtocolMessage.Code, _options.ApiScopes.Split(' '));
context.HandleCodeRedemption(authenticationResult.AccessToken, authenticationResult.IdToken);
}
catch (Exception ex)
{
// TODO: Handle.
throw;
}
}
public Task OnRedirectToIdentityProvider(RedirectContext context)
{
context.ProtocolMessage.ResponseType = OpenIdConnectResponseType.CodeIdToken;
context.ProtocolMessage.Scope += $" offline_access {_options.ApiScopes}";
return Task.FromResult(0);
}
}
在向Azure AD B2C发送身份验证请求之前,此配置类将API范围添加到身份验证请求的 scope 参数。
从Azure AD B2C接收到授权码后,配置类使用访问令牌交换该授权码,并使用Microsoft身份验证库(MSAL)将访问令牌保存到令牌缓存中。
Startup
类中注册配置类的单个实例:public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// The following line configures the Azure AD B2C authentication with API options.
services.Configure<AzureADB2CWithApiOptions>(options => Configuration.Bind("AzureADB2C", options));
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(AzureADB2CDefaults.AuthenticationScheme)
.AddAzureADB2C(options => Configuration.Bind("AzureADB2C", options));
// The following line registers the OpenID Connect authentication options for the Azure AD B2C authentication middleware.
services.AddSingleton<IConfigureOptions<OpenIdConnectOptions>, AzureADB2COpenIdConnectOptionsConfigurator>();
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
}
对于使用Microsoft身份验证库(MSAL)从令牌缓存中加载访问令牌的控制器方法,您必须:
public class HelloController : Controller
{
private readonly AzureADB2CWithApiOptions _options;
public HelloController(IOptions<AzureADB2CWithApiOptions> optionsAccessor)
{
_options = optionsAccessor.Value;
}
public async Task<IActionResult> Index()
{
var clientCredential = new ClientCredential(_options.ClientSecret);
var userId = context.Principal.FindFirst(ClaimTypes.NameIdentifier).Value;
var userTokenCache = new SessionTokenCache(HttpContext, userId);
var confidentialClientApplication = new ConfidentialClientApplication(
_options.ClientId,
_options.Authority,
_options.RedirectUri,
clientCredential,
userTokenCache.GetInstance(),
null);
var authenticationresult = await confidentialClientApplication.AcquireTokenSilentAsync(
_options.ApiScopes.Split(' '),
confidentialClientApplication.Users.FirstOrDefault(),
_options.Authority,
false);
// TODO: Invoke the API endpoint by setting the Authorization header to "Bearer" + authenticationResult.AccessToken.
}
}
答案 1 :(得分:0)
您不了解OAuth中的不同流程。在您的MVC前端中,您使用Authorization Code Grant flow来授权用户。然后在后端,您丢弃该用户并使用Client Credentials flow获取您要呼叫的服务的令牌。
在某种程度上,这很好。只要您在上次服务到服务呼叫中不需要最终用户上下文。但是,client credentials flow is currently not supported in Azure AD B2C:
守护程序/服务器端应用
包含长时间运行流程的应用或 在没有用户存在的情况下运行也需要一种访问方式 安全的资源,如Web API。这些应用可以进行身份验证和 通过使用应用程序的身份(而不是用户的委托)获取令牌 身份)并使用OAuth 2.0客户端凭据流。
Azure AD B2C目前不支持此流程。这些应用可以 只有在交互式用户流发生后才能获取令牌。
您必须使用授权代码授予流程获取所有access_tokens。 您可以查看如何实现此目的at this sample。