使用 OpenId (Cognito) 进行身份验证后,如何在 Blazor WebAssembly 中获取 id_token?
How do I get the id_token in Blazor WebAssembly after authenticating with OpenId (Cognito)?
我有一个 .Net Core 3.1 WebApi 后端。
我有一个 Blazor WebAssembly front-end。
我正在尝试在 front-end(有效)上登录 AWS Cognito(设置为 OpenId 提供程序),然后将 Bearer 令牌 (JWT) 传递到我的后端 API请求以便后端 API 可以使用临时凭证 (CognitoAWSCredentials) 访问 AWS 资源。
我可以将 Bearer 令牌从我的 Blazor front-end 的每个请求传递到后端,但是我可以在 Blazor 中找到访问的唯一令牌是访问令牌。我需要 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 服务创建用于特权的 CognitoAWSCredentials,我需要 ID Token.
我找不到在 Blazor 中访问 ID 令牌的任何方式。
如果我直接访问我的后端 WebApi,它会正确地将我转到 Cognito 进行登录,然后 return 返回。当它出现时,HttpContext 包含“id_token”。然后可以使用它来创建我需要的 CognitoAWSCredentials。
缺少的 link 是如何在 Blazor 中访问 ID 令牌,因此我可以将其作为授权 HTTP header 的 Bearer 令牌而不是访问令牌。
添加更多代码上下文....
Program.cs:主要
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
(隐藏:我为一些可能敏感的值插入)
- /oauth2/authorize 上的 Cognito 文档:https://docs.aws.amazon.com/cognito/latest/developerguide/authorization-endpoint.html
如果请求 openid 范围,则仅 return 编辑 ID 令牌。如果请求 aws.cognito.signin.user.admin 范围,则访问令牌只能用于 Amazon Cognito 用户池。
因为我的普通用户不是管理员,所以我不请求管理员范围。
所以根据文档,Cognito 应该 returning 一个 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 令牌。
感谢 How to get the id_token in blazor web assembly 上的回答,我能够得到 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。
我有一个 .Net Core 3.1 WebApi 后端。
我有一个 Blazor WebAssembly front-end。
我正在尝试在 front-end(有效)上登录 AWS Cognito(设置为 OpenId 提供程序),然后将 Bearer 令牌 (JWT) 传递到我的后端 API请求以便后端 API 可以使用临时凭证 (CognitoAWSCredentials) 访问 AWS 资源。
我可以将 Bearer 令牌从我的 Blazor front-end 的每个请求传递到后端,但是我可以在 Blazor 中找到访问的唯一令牌是访问令牌。我需要 ID 令牌才能让后端代表我的用户生成凭据。
在我的 Blazor 代码中,我已经成功注册了一个自定义 AuthorizationMessageHandler,当访问我的 API:
时,它会调用每个 HttpClient 的 SendAsyncprotected 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 服务创建用于特权的 CognitoAWSCredentials,我需要 ID Token.
我找不到在 Blazor 中访问 ID 令牌的任何方式。
如果我直接访问我的后端 WebApi,它会正确地将我转到 Cognito 进行登录,然后 return 返回。当它出现时,HttpContext 包含“id_token”。然后可以使用它来创建我需要的 CognitoAWSCredentials。
缺少的 link 是如何在 Blazor 中访问 ID 令牌,因此我可以将其作为授权 HTTP header 的 Bearer 令牌而不是访问令牌。
添加更多代码上下文....
Program.cs:主要
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
(隐藏:我为一些可能敏感的值插入)
- /oauth2/authorize 上的 Cognito 文档:https://docs.aws.amazon.com/cognito/latest/developerguide/authorization-endpoint.html
如果请求 openid 范围,则仅 return 编辑 ID 令牌。如果请求 aws.cognito.signin.user.admin 范围,则访问令牌只能用于 Amazon Cognito 用户池。
因为我的普通用户不是管理员,所以我不请求管理员范围。
所以根据文档,Cognito 应该 returning 一个 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 令牌。
感谢 How to get the id_token in blazor web assembly 上的回答,我能够得到 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。