我有一个.Net Core 3.1 WebApi后端。
我有一个Blazor WebAssembly前端。
我试图在前端(工作(登录到AWS Cognito(设置为OpenId提供程序(,然后在每次请求时向我的后端API传递承载令牌(JWT(,以便后端API可以使用临时凭据(CognitoAWSCredentials(访问AWS资源。
我可以在每次请求时将Bearer令牌从我的Blazor前端传递到后端,但我在Blazor中唯一可以访问的令牌是access token。我需要ID令牌,以便允许后端代表我的用户生成凭据。
在我的Blazor代码中,我成功注册了一个自定义AuthorizationMessageHandler,当访问我的API时,它会在每个HttpClient的SendAsync上调用:
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpRequestHeaders headers = request?.Headers;
AuthenticationHeaderValue authHeader = headers?.Authorization;
if (headers is object && authHeader is null)
{
AccessTokenResult result = await TokenProvider.RequestAccessToken();
if (result.TryGetToken(out AccessToken token))
{
authHeader = new AuthenticationHeaderValue("Bearer", token.Value);
request.Headers.Authorization = authHeader;
}
logger.LogObjectDebug(request);
}
return await base.SendAsync(request, cancellationToken);
}
这会添加访问令牌,后端会拾取令牌并对其进行良好验证。然而,要为AWS服务创建CognitoAWSC凭据以用于特权,我需要ID令牌。
我找不到任何方法来访问Blazor中的ID令牌
如果我直接访问我的后端WebApi,它会正确地将我转发到Cognito登录,然后返回。当它这样做时,HttpContext包含";id_token";。然后可以使用它来创建我需要的CognitoAWSC基本信息。
缺少的链接是如何访问Blazor中的ID令牌,因此我可以将其作为授权HTTP标头的承载令牌,而不是访问令牌。
添加更多的代码上下文。。。。
程序.cs:Main
string CognitoMetadataAddress = $"{settings.Cognito.Authority?.TrimEnd('/')}/.well-known/openid-configuration";
builder.Services.AddOidcAuthentication<RemoteAuthenticationState, CustomUserAccount>(options =>
{
options.ProviderOptions.Authority = settings.Cognito.Authority;
options.ProviderOptions.MetadataUrl = CognitoMetadataAddress;
options.ProviderOptions.ClientId = settings.Cognito.ClientId;
options.ProviderOptions.RedirectUri = $"{builder.HostEnvironment.BaseAddress.TrimEnd('/')}/authentication/login-callback";
options.ProviderOptions.ResponseType = OpenIdConnectResponseType.Code;
})
.AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, CustomUserAccount, CustomAccountFactory>()
;
builder.Services.AddOptions();
builder.Services.AddAuthorizationCore();
string APIBaseUrl = builder.Configuration.GetSection("Deployment")["APIBaseUrl"];
builder.Services.AddSingleton<CustomAuthorizationMessageHandler>();
builder.Services.AddHttpClient(settings.HttpClientName, client =>
{
client.BaseAddress = new Uri(APIBaseUrl);
})
.AddHttpMessageHandler<CustomAuthorizationMessageHandler>()
;
正在发送http请求(Blazor示例代码的微小更改(
HttpRequestMessage requestMessage = new HttpRequestMessage()
{
Method = new HttpMethod(method),
RequestUri = new Uri(uri),
Content = string.IsNullOrEmpty(requestBody) ? null : new StringContent(requestBody)
};
foreach (RequestHeader header in requestHeaders)
{
// StringContent automatically adds its own Content-Type header with default value "text/plain"
// If the developer is trying to specify a content type explicitly, we need to replace the default value,
// rather than adding a second Content-Type header.
if (header.Name.Equals("Content-Type", StringComparison.OrdinalIgnoreCase) && requestMessage.Content != null)
{
requestMessage.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(header.Value);
continue;
}
if (!requestMessage.Headers.TryAddWithoutValidation(header.Name, header.Value))
{
requestMessage.Content?.Headers.TryAddWithoutValidation(header.Name, header.Value);
}
}
HttpClient Http = HttpClientFactory.CreateClient(Settings.HttpClientName);
HttpResponseMessage response = await Http.SendAsync(requestMessage);
当OpenIdConnect中间件尝试使用Cognito进行授权时,它调用:
https://<DOMAIN>/oauth2/authorize?client_id=<CLIENT-ID>&redirect_uri=https%3A%2F%2Flocalhost%3A44356%2Fauthentication%2Flogin-callback&response_type=code&scope=openid%20profile&state=<HIDDEN>&code_challenge=<HIDDEN>&code_challenge_method=S256&response_mode=query
(HIDDEN:我为一些可能敏感的值插入(
- /oauth2/授权上的Cognito文档:https://docs.aws.amazon.com/cognito/latest/developerguide/authorization-endpoint.html
只有在请求openid作用域时才返回ID令牌。只有在请求了aws.Cognito.signin.User.admin作用域的情况下,才能对Amazon Cognito用户池使用访问令牌
由于我的普通用户不是管理员,所以我没有请求管理员范围。
因此,根据文档,Cognito应该返回一个ID令牌。当我打印出由Blazor中的OIDC中间件创建的ClaimsPrincipal的声明时,token_use是id
:
{
"Type": "token_use",
"Value": "id",
"ValueType": "http://www.w3.org/2001/XMLSchema#string",
"Subject": null,
"Properties": {},
"OriginalIssuer": "LOCAL AUTHORITY",
"Issuer": "LOCAL AUTHORITY"
}
但是,添加到Http请求的AccessToken是一个access_token。以下是添加到HTTP请求的解码JWT令牌的token_use
声明:
{
"Type": "token_use",
"Value": "access",
"ValueType": "http://www.w3.org/2001/XMLSchema#string",
"Subject": null,
"Properties": {},
"OriginalIssuer": "https://cognito-idp.ca-central-1.amazonaws.com/<USER-POOL-ID>",
"Issuer": "https://cognito-idp.ca-central-1.amazonaws.com/<USER-POOL-ID>"
}
由于Blazor API是IAccessTokenProvider.RequestAccessToken()
。。。似乎没有API来请求ID令牌。
多亏了关于如何在blazor web程序集中获得id_token的答案,我才能够获得id_token。下面的示例代码:
@page "/"
@using System.Text.Json
@inject IJSRuntime JSRuntime
<AuthorizeView>
<Authorized>
<div>
<b>CachedAuthSettings</b>
<pre>
@JsonSerializer.Serialize(authSettings, indented);
</pre>
<br/>
<b>CognitoUser</b><br/>
<pre>
@JsonSerializer.Serialize(user, indented);
</pre>
</div>
</Authorized>
<NotAuthorized>
<div class="alert alert-warning" role="alert">
Everything requires you to <a href="/authentication/login">Log In</a> first.
</div>
</NotAuthorized>
</AuthorizeView>
@code {
JsonSerializerOptions indented = new JsonSerializerOptions() { WriteIndented = true };
CachedAuthSettings authSettings;
CognitoUser user;
protected override async Task OnInitializedAsync()
{
string key = "Microsoft.AspNetCore.Components.WebAssembly.Authentication.CachedAuthSettings";
string authSettingsRAW = await JSRuntime.InvokeAsync<string>("sessionStorage.getItem", key);
authSettings = JsonSerializer.Deserialize<CachedAuthSettings>(authSettingsRAW);
string userRAW = await JSRuntime.InvokeAsync<string>("sessionStorage.getItem", authSettings?.OIDCUserKey);
user = JsonSerializer.Deserialize<CognitoUser>(userRAW);
}
public class CachedAuthSettings
{
public string authority { get; set; }
public string metadataUrl { get; set; }
public string client_id { get; set; }
public string[] defaultScopes { get; set; }
public string redirect_uri { get; set; }
public string post_logout_redirect_uri { get; set; }
public string response_type { get; set; }
public string response_mode { get; set; }
public string scope { get; set; }
public string OIDCUserKey => $"oidc.user:{authority}:{client_id}";
}
public class CognitoUser
{
public string id_token { get; set; }
public string access_token { get; set; }
public string refresh_token { get; set; }
public string token_type { get; set; }
public string scope { get; set; }
public int expires_at { get; set; }
}
}
编辑:但是。。。如果您将id_token与CognitoAWSCredentials一起使用,那么您将遇到此错误(https://github.com/aws/aws-sdk-net/pull/1603)其正在等待合并。如果没有它,您将无法直接在Blazor WebAssembly中使用AWS SDK客户端,只需将id_token传递到您的后端it即可创建CognitoAWSCredentials。