简体   繁体   English

使用Blazor使用Refresh Token实施短暂的Jwt

[英]Implementing short lived Jwt with Refresh Token with Blazor

We are currently developing a Blazor app which is secured using short lived (10 minute) Jwt with Refresh Tokens. 我们目前正在开发Blazor应用,该应用使用具有刷新令牌的短暂(10分钟)Jwt进行保护。

Currently we have the Jwt implemented and through the Blazor server side web api can login, generate the Jwt and generate the refresh token. 当前,我们已经实现了Jwt,并且通过Blazor服务器端Web api可以登录,生成Jwt并生成刷新令牌。

From the client side I have used the following link; 在客户端,我使用以下链接;

Authentication With client-side Blazor 使用客户端Blazor进行身份验证

and extended the ApiAuthenticationStateProvider.cs as follows; 并如下扩展ApiAuthenticationStateProvider.cs

public class ApiAuthenticationStateProvider : AuthenticationStateProvider
{
    private readonly HttpClient _httpClient;
    private readonly ILocalStorageService _localStorage;

    public ApiAuthenticationStateProvider(HttpClient httpClient, ILocalStorageService localStorage)
    {
        _httpClient = httpClient;
        _localStorage = localStorage;
    }
    public override async Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        var savedToken = await _localStorage.GetItemAsync<string>("authToken");
        var refreshToken = await _localStorage.GetItemAsync<string>("refreshToken");

        if (string.IsNullOrWhiteSpace(savedToken) || string.IsNullOrWhiteSpace(refreshToken))
        {
            return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
        }

        var userResponse = await _httpClient.GetAsync<UserModel>("api/accounts/user", savedToken);

        if(userResponse.HasError)
        {
            var response = await _httpClient.PostAsync<LoginResponse>("api/login/refreshToken", new RefreshTokenModel { RefreshToken = refreshToken });

            //check result now
            if (!response.HasError)
            {
                await _localStorage.SetItemAsync("authToken", response.Result.AccessToken);
                await _localStorage.SetItemAsync("refreshToken", response.Result.RefreshToken);

                userResponse = await _httpClient.GetAsync<UserModel>("api/accounts/user", response.Result.AccessToken);
            }

        }

        var identity = !userResponse.HasError ? new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, userResponse.Result.Email) }, "apiauth") : new ClaimsIdentity();

        return new AuthenticationState(new ClaimsPrincipal(identity));
    }

    public void MarkUserAsAuthenticated(string email)
    {
        var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, email) }, "apiauth"));
        var authState = Task.FromResult(new AuthenticationState(authenticatedUser));
        NotifyAuthenticationStateChanged(authState);
    }

    public void MarkUserAsLoggedOut()
    {
        var anonymousUser = new ClaimsPrincipal(new ClaimsIdentity());
        var authState = Task.FromResult(new AuthenticationState(anonymousUser));
        NotifyAuthenticationStateChanged(authState);
    }
}

So if the Jwt fails the first time we try to renew with the refresh token. 因此,如果Jwt第一次失败,我们将尝试使用刷新令牌进行续订。

The code above is working, however the first issue i found is, if I then navigate to the /fetchData test end point (which is protected with the [Authorize] attribute). 上面的代码正在运行,但是,如果我随后导航到/fetchData测试端点(受[Authorize]属性保护),则发现的第一个问题是。 The page initially runs fine and sends the Jwt in the header. 该页面最初运行正常,并在标头中发送Jwt。 However, if i then f5 and refresh the page I get a 401 unauthorized on the /fecthData endpoint, ie on the code; 但是,如果我然后按f5并刷新页面,则在/fecthData端点(即代码) /fecthData获得401未经授权的信息;

@code {
    WeatherForecast[] forecasts;

    protected override async Task OnInitAsync()
    {
        forecasts = await Http.GetJsonAsync<WeatherForecast[]>("api/SampleData/WeatherForecasts");
    }
} 

Now if to get around this I can manually add the Jwt form localStorage to the header (in my case I use an extension method); 现在,如果要解决此问题,我可以手动将Jwt表单localStorage添加到标题中(在我的情况下,我使用扩展方法);

public static async Task<ServiceResponse<T>> GetAsync<T>(
        this HttpClient httpClient, string url, string token)
    {
        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
        var response = await httpClient.GetAsync(url);

        return await BuildResponse<T>(response);
    }

However, the second issue I have here is that if the Jwt expires during this call I would need to call to use the refresh token to get a new Jwt. 但是,我这里遇到的第二个问题是,如果Jwt在此调用期间过期,则需要调用以使用刷新令牌来获取新的Jwt。

Is there a way I can do this do this with middleware to avoid having to check for a 401 on each call and then renewing the token this way? 有没有办法我可以使用中间件来做到这一点,从而避免每次调用时都要检查401,然后以这种方式更新令牌?

So often, we are thinking on Blazor as an MVC but it is not. 很多时候,我们考虑将Blazor作为MVC,但事实并非如此。 It's more like a desktop app running inside browser. 它更像是在浏览器中运行的桌面应用程序。 I use JWT and renewing tokens in this way: after login, I have an infinite loop that is pinging backend and keeping the session and renewing the tokens. 我以这种方式使用JWT和更新令牌:登录后,我遇到一个无限循环,该循环对后端执行ping操作并保持会话并更新令牌。 Simplifying: 简化:

class JWTAuthenticationStateProvider : AuthenticationStateProvider
{
    private bool IsLogedIn = false;
    private CustomCredentials credentials = null;
    // private ClaimsPrincipal currentClaimsPrincipal = null; (optinally)
    public Task Login( string user, string password )
    {
         credentials = go_backend_login_service( user, password );
         // do stuff with credentials and claims
         // I raise event here to notify login
         keepSession( );
    }
    public Task Logout(  )
    {
         go_bakcend_logout_service( credentials );
         // do stuff with claims
         IsLogedIn = false;
         // I raise event here to notify logout
    }
    public override Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        // make a response from credentials or currentClaimsPrincipal
    }
    private async void KeepSession()
    {
        while(IsLogedIn)
        {
            credentials = go_backend_renewingJWT_service( credentials );
            // do stuff with new credentials: check are ok, update IsLogedIn, ...
            // I raise event here if server says logout
            await Task.Delay(1000);  // sleep for a while.
        }
    }
}

Remember to register component by DI: 请记住通过DI注册组件:

public void ConfigureServices(IServiceCollection services)
{
    // ... other services added here ...

    // One JWTAuthenticationStateProvider for each connection on server side.
    // A singleton for clientside.
    services.AddScoped<AuthenticationStateProvider, 
                       JWTAuthenticationStateProvider>();
}

This is just one idea, you should to think about it and adapt it to your own solution. 这只是一个主意,您应该考虑一下并使其适应您自己的解决方案。

More about Authentication and Authorization on github SteveSandersonMS/blazor-auth.md 有关github SteveSandersonMS / blazor-auth.md上身份验证和授权的更多信息

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM