如何在blazor web assembly中获取id_token

问题描述 投票:0回答:5

我有一个带有 oidc 身份验证的 Blazor WebAssembly(最新 3.2.0)应用程序。 asp.net 身份验证提供了一种获取 accessToken 的方法,但看不到任何访问我的方案所需的 id_token (jwt) 的方法。 我可以在浏览器的本地存储中看到id_token。 访问它的最佳方式是什么?

谢谢

asp.net-core openid-connect blazor blazor-webassembly
5个回答
7
投票

您可以使用 JSInterop 从会话存储中读取它,它存储在键 oidc.user:{app baseUri}:{app client id} :

@inject IJSRuntime JSRuntime
@inject NavigationManager NavigationManager
...
@code {
     private async Task<string> ReadIdToken()
     {
          const string clientId = "your oidc client id";
          var userDataKey = $"oidc.user:{NavigationManager.BaseUri}:{clientId}";
          var userData = await JSRuntime.InvokeAsync<UserData>("sessionStorage.getItem", userDataKey);
          return userData.id_token;          
     }

     class UserData
     {
         public string id_token { get; set; }
         public int expires_at { get; set; }
     }
}

2
投票

这是一个工作代码示例,允许您获取原始格式的 id_token 以及从中解析的声明列表。

注意:您应该先进行身份验证,然后才能看到结果...

@page "/"

@inject IJSRuntime JSRuntime
@inject NavigationManager NavigationManager

@using System.Security.Claims
@using System.Text.Json

<p>@JwtToken</p>

@foreach (var claim in claims)
{
    <p>@claim</p>
}


@code {
      List<Claim> claims = new List<Claim>();
      string JwtToken;


    protected override async Task OnInitializedAsync()
    {
        await GetJwtToken();

    }
    private async Task GetJwtToken()
    {
        var baseUri = NavigationManager.BaseUri.Substring(0, 
                              NavigationManager.BaseUri.Length - 1);
        // client id example: RoleBasedApiAuthorization.Client 
        const string clientID = "<Place here your client id>";
        var key = $"oidc.user:{baseUri}:{clientID}";
        JwtToken = await JSRuntime.InvokeAsync<string> 
                                    ("sessionStorage.getItem", key);

        if (JwtToken != null)
        {
            claims = ParseClaimsFromJwt(JwtToken).ToList();
        }

    }


    public IEnumerable<Claim> ParseClaimsFromJwt(string jwt)
    {
        var payload = jwt.Split('.')[1];
        var jsonBytes = ParseBase64WithoutPadding(payload);
        var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);
        return keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString()));
    }

    private byte[] ParseBase64WithoutPadding(string base64)
    {
        switch (base64.Length % 4)
        {
            case 2: base64 += "=="; break;
            case 3: base64 += "="; break;
        }
        return Convert.FromBase64String(base64);
    }
}

1
投票

非常感谢你们 - 我已经为此苦苦挣扎了一个星期(哦 - 忘记查看 Chrome 中的浏览器会话数据来考虑使用 JRRuntime...)。

我不确定这是否是 Cognito 特定的,但对我来说关键不是使用 NavigationManager BaseUri,而是使用 OIDC Authority。

@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; }
    }
}

如果我直接尝试使用 JSRuntme.InvokeAsync 将字符串转换为类,则会出现序列化错误,但它与 JsonSerializer 配合得很好,这就是为什么您会看到看似额外的步骤。


1
投票

我终于让它与我的 Blazor 7.0 WASM 应用程序一起使用,该应用程序通过 OIDC 与 Google 进行身份验证,并且需要将“ID 令牌”发送到我的 ASP.NET Web API。我花了很长时间才弄清楚这个问题。如上所述,“访问令牌”是中间件中默认在“授权”标头中设置的内容。相反,ASP.NET Web API 需要 JWT,即“ID 令牌”。

正如其他响应者所提到的,这只能通过 Javascript 访问。我在 Blazor 应用程序中所做的事情是从 AuthorizationHandler 创建一个派生类,如下所示:

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.JSInterop;

namespace ExperiencePlatform.Client.Infrastructure
{
    public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
    {        
        private NavigationManager _navigation;
        private IConfiguration _configuration;
        private IJSRuntime _jsRuntime;
        public CustomAuthorizationMessageHandler(IAccessTokenProvider provider, NavigationManager navigation, IConfiguration configuration, IJSRuntime jsRuntime) : base(provider, navigation)
        {
            _jsRuntime = jsRuntime;
            _configuration = configuration;
            _navigation = navigation;
            ConfigureHandler(
            authorizedUrls: new[] { configuration[Constants.ExperiencePlatformServerKey] },
            scopes: new[] { "openid" });     
        }

        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {

            string idToken = await _jsRuntime.InvokeAsync<string>("getOidToken", $"oidc.user:{_configuration["Google:Authority"]}:{_configuration["Google:ClientId"]}");
            request.Headers.Add(ExperiencePlatform.Shared.Authentication.Constants.IdTokenHttpHeader, $"Bearer {idToken}");
            return await base.SendAsync(request, cancellationToken);
        }         
    }
}

Javascript 中的“getOidToken”函数如下所示:

function getOidToken(key) {
    var strJson = sessionStorage.getItem(key);
    if (strJson != null) {
        var json = JSON.parse(strJson);
        return json.id_token;
    }

    return null;
}

请记住在 Blazor WASM 的 Program.cs 中设置依赖项注入并挂钩到 CustomAuthorizationMessageHandler 类,如下所示:

builder.Services.AddTransient<CustomAuthorizationMessageHandler>();

builder.Services.AddHttpClient(Constants.ExperiencePlatformServerKey, client => client.BaseAddress = new Uri(config[Constants.ExperiencePlatformServerKey]))
    .AddHttpMessageHandler<CustomAuthorizationMessageHandler>();

builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient(Constants.ExperiencePlatformServerKey));

最后,在 ASP.NET Web API 端,假设您有一个控制器,其端点定义如下:

[ApiController]
[Authorize]
[Route("[controller]")]
public class ExperiencesController : ControllerBase {...}

在您的 Program.cs 中,确保处理“OnMessageReceived”JwtBearerEvent 并获取自定义授权标头。这是完整的代码块:

builder.Services
    .AddAuthentication(options =>
    {
        options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(options =>
    {
        options.Authority = builder.Configuration["Google:Authority"];
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidIssuer = builder.Configuration["Google:Authority"],
            ValidAudience = builder.Configuration["Google:ClientId"],
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            RequireExpirationTime = true,
            ValidateIssuerSigningKey = true
        };
        options.Events = new JwtBearerEvents
        {
            OnMessageReceived = context =>
            {
                string idTokenHeader = context.Request.Headers[ExperiencePlatform.Shared.Authentication.Constants.IdTokenHttpHeader];

                if (!string.IsNullOrEmpty(idTokenHeader) && idTokenHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
                {
                    string token = idTokenHeader.Substring("Bearer ".Length).Trim();
                    context.Token = token;
                }
                
                return Task.CompletedTask;
            }            
        };

    });

其次是未按正确顺序定义中间件的常见嫌疑:

app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();   
app.UseHttpsRedirection();
app.MapControllers();

app.Run();

© www.soinside.com 2019 - 2024. All rights reserved.