Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement access token handling #32

Merged
merged 1 commit into from
Jan 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 48 additions & 18 deletions src/iRLeagueManager.Web/Data/AsyncTokenStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,41 +9,45 @@ internal sealed class AsyncTokenStore : ITokenStore
private readonly ILogger<AsyncTokenStore> logger;
private readonly ProtectedLocalStorage localStore;

private const string tokenKey = "LeagueApiToken";
private const string tokenKey = "idToken";

private string inMemoryToken = string.Empty;
private string inMemoryIdToken = string.Empty;
private string inMemoryAccessToken = string.Empty;

public event EventHandler? TokenChanged;
public event EventHandler? TokenExpired;

public bool IsLoggedIn { get; private set; }
public DateTime Expiration { get; private set; }
private bool AccessTokenExpired { get; set; } = false;
public DateTime IdTokenExpires { get; private set; }
public DateTime AccessTokenExpires { get; private set; }

public AsyncTokenStore(ILogger<AsyncTokenStore> logger, ProtectedLocalStorage localStorage)
{
this.logger = logger;
this.localStore = localStorage;
}

public async Task ClearTokenAsync()
public async Task ClearTokensAsync()
{
var tokenValue = inMemoryToken;
var tokenValue = inMemoryIdToken;

logger.LogDebug("Clear token in local browser store");
IsLoggedIn = false;
inMemoryToken = string.Empty;
inMemoryIdToken = string.Empty;
await localStore.DeleteAsync(tokenKey);
await Task.FromResult(true);
if (inMemoryToken != tokenValue)
if (inMemoryIdToken != tokenValue)
{
TokenChanged?.Invoke(this, EventArgs.Empty);
}
}

public async Task<string> GetTokenAsync()
public async Task<string> GetIdTokenAsync()
{
if (string.IsNullOrEmpty(inMemoryToken) == false)
if (string.IsNullOrEmpty(inMemoryIdToken) == false)
{
return inMemoryToken;
return inMemoryIdToken;
}

logger.LogDebug("Reading token from local browser store");
Expand All @@ -65,19 +69,19 @@ public async Task<string> GetTokenAsync()
if (jsonToken.Claims.Any(x => x.Type == "exp"))
{
var expSeconds = Convert.ToInt64(jsonToken.Claims.First(x => x.Type == "exp").Value);
Expiration = new DateTime(1970, 1, 1).AddSeconds(expSeconds);
IdTokenExpires = new DateTime(1970, 1, 1).AddSeconds(expSeconds);
}
}

// check if token is still valid
if (Expiration < DateTime.UtcNow.AddMinutes(5))
if (IdTokenExpires < DateTime.UtcNow.AddMinutes(5))
{
await ClearTokenAsync();
await ClearTokensAsync();
logger.LogInformation("Token read from token store has expired");
return string.Empty;
}
IsLoggedIn = true;
return inMemoryToken = token.Value ?? string.Empty;
return inMemoryIdToken = token.Value ?? string.Empty;
}
IsLoggedIn = false;
return string.Empty;
Expand All @@ -89,16 +93,42 @@ public async Task<string> GetTokenAsync()
}
}

public async Task SetTokenAsync(string token)
public async Task SetIdTokenAsync(string token)
{
var oldToken = inMemoryToken;
var oldToken = inMemoryIdToken;
logger.LogDebug("Set token to local browser session: {Token}", token);
await localStore.SetAsync(tokenKey, token);
inMemoryToken = token;
inMemoryIdToken = token;

if (inMemoryToken != oldToken)
if (inMemoryIdToken != oldToken)
{
TokenChanged?.Invoke(this, EventArgs.Empty);
}
}

public async Task SetAccessTokenAsync(string token)
{
inMemoryAccessToken = token;
AccessTokenExpired = false;

if (string.IsNullOrEmpty(inMemoryAccessToken) == false)
{
// set expiration date
var jwtToken = new JwtSecurityTokenHandler().ReadToken(inMemoryAccessToken);
AccessTokenExpires = jwtToken.ValidTo;
}

TokenChanged?.Invoke(this, EventArgs.Empty);
await Task.CompletedTask;
}

public async Task<string> GetAccessTokenAsync()
{
if (AccessTokenExpires <= DateTime.UtcNow && string.IsNullOrEmpty(inMemoryAccessToken) == false && AccessTokenExpired == false)
{
AccessTokenExpired = true;
TokenExpired?.Invoke(this, EventArgs.Empty);
}
return await Task.FromResult(inMemoryAccessToken);
}
}
2 changes: 1 addition & 1 deletion src/iRLeagueManager.Web/Pages/Member/Login.razor
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (string.IsNullOrEmpty(await tokenStore.GetTokenAsync()) == false)
if (string.IsNullOrEmpty(await tokenStore.GetAccessTokenAsync()) == false)
{
var returnUrl = NavigationManager.QueryString("returnUrl") ?? "";
NavigationManager.NavigateTo(returnUrl);
Expand Down
11 changes: 8 additions & 3 deletions src/iRLeagueManager.Web/Shared/JwtAuthenticationStateProvicer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,17 @@ private void TokenStore_TokenChanged(object? sender, EventArgs e)

private async Task<ClaimsPrincipal> GetTokenUser()
{
var token = await tokenStore.GetTokenAsync();
if (string.IsNullOrEmpty(token))
var idToken = await tokenStore.GetIdTokenAsync();
if (string.IsNullOrEmpty(idToken))
{
return GetAnonymous();
}
var jwtSecurityToken = tokenHandler.ReadJwtToken(token);
var accessToken = await tokenStore.GetAccessTokenAsync();
if (string.IsNullOrEmpty(accessToken))
{
return GetAnonymous();
}
var jwtSecurityToken = tokenHandler.ReadJwtToken(accessToken);
var identity = new ClaimsIdentity(jwtSecurityToken.Claims, "bearer");
return new ClaimsPrincipal(identity);
}
Expand Down
4 changes: 2 additions & 2 deletions src/iRLeagueManager.Web/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
}
},
"AllowedHosts": "*",
//"APIServer": "http://localhost:5000",
"APIServer": "https://irleaguemanager.net/api/",
"APIServer": "http://localhost:5000",
//"APIServer": "https://irleaguemanager.net/api/",
"DefaultUser": "testuser",
"DefaultPassword": "TestPass123!"
}
8 changes: 2 additions & 6 deletions src/iRLeagueManager.Web/iRLeagueManager.Web.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,12 @@
<Content Include="Components\Reviews\ReviewCard.razor.css" />
</ItemGroup>

<ItemGroup>
<None Include="compilerconfig.json" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Blazored.LocalStorage" Version="4.3.0" />
<PackageReference Include="Blazored.Modal" Version="7.1.0" />
<PackageReference Include="bootstrap.sass" Version="5.2.3" />
<PackageReference Include="iRLeagueApiCore.Client" Version="0.4.1" />
<PackageReference Include="iRLeagueApiCore.Common" Version="0.4.1" />
<PackageReference Include="iRLeagueApiCore.Client" Version="0.4.2-dev.3" />
<PackageReference Include="iRLeagueApiCore.Common" Version="0.4.2-dev.1" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="6.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="6.0.3" />
Expand Down