Skip to content

Commit

Permalink
107: Added logic to store auth tokens in local storage
Browse files Browse the repository at this point in the history
  • Loading branch information
jarmatys committed Jun 24, 2024
1 parent 41c3ded commit b794aaa
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 102 deletions.
6 changes: 3 additions & 3 deletions API/ASSISTENTE.UI.Auth/Models/AuthError.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

namespace ASSISTENTE.UI.Auth.Models;

public class AuthError
public sealed class AuthError(string error, string description)
{
[JsonProperty("error")]
public string? Error { get; set; }
public string? Error { get; set; } = error;

[JsonProperty("error_description")]
public string? Description { get; set; }
public string? Description { get; set; } = description;
}
24 changes: 24 additions & 0 deletions API/ASSISTENTE.UI.Auth/Models/AuthResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace ASSISTENTE.UI.Auth.Models;

public sealed class AuthResponse
{
private AuthResponse()
{
IsSuccess = true;
}

private AuthResponse(AuthError error)
{
IsSuccess = false;
Error = error;
}

public bool IsSuccess { get; set; }
public AuthError? Error { get; set; }

public static AuthResponse Success() => new();

public static AuthResponse Fail(AuthError? error) => error != null
? new AuthResponse(error)
: new AuthResponse(new AuthError("UnknownError", "Unknown error"));
}
2 changes: 1 addition & 1 deletion API/ASSISTENTE.UI.Auth/Models/AuthStore.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
namespace ASSISTENTE.UI.Auth.Models;

public record AuthStore(string AccessToken, string RefreshToken);
public sealed record AuthStore(string AccessToken, string RefreshToken);
27 changes: 11 additions & 16 deletions API/ASSISTENTE.UI.Auth/Pages/Auth/Login.razor
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@

bool _isSuccess;
bool _isLoading;

readonly GeneralError _generalError = new();
ValidationMessageStore _messageStore = null!;
EditContext _editContext = null!;
Expand All @@ -58,30 +58,25 @@
private async Task OnValidSubmit(EditContext context)
{
_isLoading = true;

var result = await AuthProvider.LoginAsync(new LoginDto(_model.Email!, _model.Password!));

_isLoading = false;
_isSuccess = result.IsSuccess;

try
if (result.IsSuccess)
{
await AuthProvider.LoginAsync(new LoginDto(_model.Email!, _model.Password!));

_isSuccess = true;

NavigationManager.NavigateTo("/");
}
catch (Exception ex)
else
{
var error = JsonConvert.DeserializeObject<AuthError>(ex.Message);

_messageStore.Add(() => _generalError.Error, error != null
? error.Description!
_messageStore.Add(() => _generalError.Error, result.Error != null
? result.Error.Description!
: "Something went wrong - try again...");

_isSuccess = false;

_editContext.NotifyValidationStateChanged();
}

_isLoading = false;


StateHasChanged();

_messageStore.Clear();
Expand Down
17 changes: 5 additions & 12 deletions API/ASSISTENTE.UI.Auth/Pages/Auth/Redirect.razor
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,18 @@
<PageTitle>Redirect login...</PageTitle>

@code {

string? AccessToken { get; set; }
string? RefreshToken { get; set; }

protected override async Task OnInitializedAsync()
{
AccessToken = NavigationManager.GetQueryString<string>("access_token");
RefreshToken = NavigationManager.GetQueryString<string>("refresh_token");

try
{
await AuthProvider.LoginAsync(new RedirectDto(AccessToken!, RefreshToken!));
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}

NavigationManager.NavigateTo("/");
var result = await AuthProvider.LoginAsync(new RedirectDto(AccessToken!, RefreshToken!));

NavigationManager.NavigateTo(result.IsSuccess ? "/" : "/auth/login");
}

}
21 changes: 8 additions & 13 deletions API/ASSISTENTE.UI.Auth/Pages/Auth/Register.razor
Original file line number Diff line number Diff line change
Expand Up @@ -63,29 +63,24 @@
{
_isLoading = true;

try
{
await AuthProvider.RegisterAsync(new RegisterDto(_model.Username!, _model.Email!, _model.Password!));
var result = await AuthProvider.RegisterAsync(new RegisterDto(_model.Username!, _model.Email!, _model.Password!));

_isSuccess = true;
_isSuccess = result.IsSuccess;
_isLoading = false;

if (_isSuccess)
{
NavigationManager.NavigateTo("/auth/login");
}
catch (Exception ex)
else
{
var error = JsonConvert.DeserializeObject<AuthError>(ex.Message);

_messageStore.Add(() => _generalError.Error, error != null
? error.Description!
_messageStore.Add(() => _generalError.Error, result.Error != null
? result.Error.Description!
: "Something went wrong - try again...");

_isSuccess = false;

_editContext.NotifyValidationStateChanged();
}

_isLoading = false;

StateHasChanged();

_messageStore.Clear();
Expand Down
140 changes: 86 additions & 54 deletions API/ASSISTENTE.UI.Auth/Providers/AuthStateProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@
using ASSISTENTE.UI.Auth.Providers.Models;
using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components.Authorization;
using Newtonsoft.Json;
using Supabase.Gotrue;

namespace ASSISTENTE.UI.Auth.Providers
{
public class AuthStateProvider(ILocalStorageService localStorage, SubabaseClient supabaseClient)
public class AuthStateProvider(ILocalStorageService localStorage, SubabaseClient supabaseClient)
: AuthenticationStateProvider
{
private readonly JwtSecurityTokenHandler _jwtSecurityTokenHandler = new();

public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var user = new ClaimsPrincipal(new ClaimsIdentity());

var authStore = await localStorage.GetItemAsync<AuthStore>("auth");

if (authStore?.AccessToken == null)
Expand All @@ -30,90 +31,121 @@ public override async Task<AuthenticationState> GetAuthenticationStateAsync()
if (tokenContent.ValidTo < DateTime.Now)
{
// TODO: Logic to refresh token

return new AuthenticationState(user);
}

var claims = GetClaims(authStore?.AccessToken!);

user = new ClaimsPrincipal(new ClaimsIdentity(claims, "jwt"));

return new AuthenticationState(user);
return await AuthState(authStore?.AccessToken);
}

public async Task LoginAsync(RedirectDto model)
public async Task<AuthResponse> LoginAsync(RedirectDto model)
{
// TODO: parsed error to AuthError.cs

await supabaseClient.Auth.SetSession(model.AccessToken, model.RefreshToken);
try
{
await supabaseClient.Auth.SetSession(model.AccessToken, model.RefreshToken);

await localStorage.SetItemAsync("auth", new AuthStore(model.AccessToken, model.RefreshToken));
await localStorage.SetItemAsync("auth", new AuthStore(model.AccessToken, model.RefreshToken));

var claims = GetClaims(model.AccessToken);
var authState = AuthState(model.AccessToken);

var user = new ClaimsPrincipal(new ClaimsIdentity(claims, "jwt"));
NotifyAuthenticationStateChanged(authState);

var authState = Task.FromResult(new AuthenticationState(user));
return AuthResponse.Success();
}
catch (Exception ex)
{
var authError = JsonConvert.DeserializeObject<AuthError>(ex.Message);

NotifyAuthenticationStateChanged(authState);
return AuthResponse.Fail(authError);
}
}

public async Task LoginAsync(LoginDto model)
public async Task<AuthResponse> LoginAsync(LoginDto model)
{
// TODO: parsed error to AuthError.cs

var result = await supabaseClient.Auth.SignIn(model.Email, model.Password);

await localStorage.SetItemAsync("auth", new AuthStore(result?.AccessToken!, result?.RefreshToken!));

var claims = GetClaims(result?.AccessToken!);
try
{
var result = await supabaseClient.Auth.SignIn(model.Email, model.Password);

var user = new ClaimsPrincipal(new ClaimsIdentity(claims, "jwt"));
await localStorage.SetItemAsync("auth", new AuthStore(result?.AccessToken!, result?.RefreshToken!));

var authState = AuthState(result?.AccessToken);

NotifyAuthenticationStateChanged(authState);

var authState = Task.FromResult(new AuthenticationState(user));
return AuthResponse.Success();
}
catch (Exception ex)
{
var authError = JsonConvert.DeserializeObject<AuthError>(ex.Message);

NotifyAuthenticationStateChanged(authState);
return AuthResponse.Fail(authError);
}
}

public async Task RegisterAsync(RegisterDto model)
public async Task<AuthResponse> RegisterAsync(RegisterDto model)
{
// TODO: parsed error to AuthError.cs

var registerOptions = new SignUpOptions
try
{
RedirectTo = "http://localhost:1008/auth/redirect",
Data = new Dictionary<string, object>
var registerOptions = new SignUpOptions
{
{ "display_name", model.Username }
}
};

var user = await supabaseClient.Auth.SignUp(
Constants.SignUpType.Email,
model.Email,
model.Password,
registerOptions);
RedirectTo = "http://localhost:1008/auth/redirect",
Data = new Dictionary<string, object>
{
{ "display_name", model.Username }
}
};

var user = await supabaseClient.Auth.SignUp(
Constants.SignUpType.Email,
model.Email,
model.Password,
registerOptions);

return AuthResponse.Success();
}
catch (Exception ex)
{
var authError = JsonConvert.DeserializeObject<AuthError>(ex.Message);

return AuthResponse.Fail(authError);
}
}

public async Task LogoutAsync()
public async Task<AuthResponse> LogoutAsync()
{
await localStorage.ClearAsync();
try
{
await localStorage.ClearAsync();

await supabaseClient.Auth.SignOut();

var nobody = new ClaimsPrincipal(new ClaimsIdentity());
await supabaseClient.Auth.SignOut();

var authState = Task.FromResult(new AuthenticationState(nobody));
NotifyAuthenticationStateChanged(AuthState());

NotifyAuthenticationStateChanged(authState);
return AuthResponse.Success();
}
catch (Exception ex)
{
var authError = JsonConvert.DeserializeObject<AuthError>(ex.Message);

return AuthResponse.Fail(authError);
}
}

private List<Claim> GetClaims(string token)
private Task<AuthenticationState> AuthState(string? accessToken = null)
{
var tokenContent = _jwtSecurityTokenHandler.ReadJwtToken(token);
var claims = tokenContent.Claims.ToList();
var user = new ClaimsPrincipal(new ClaimsIdentity());

if (string.IsNullOrEmpty(accessToken))
return Task.FromResult(new AuthenticationState(user));

var claims = _jwtSecurityTokenHandler
.ReadJwtToken(accessToken)
.Claims
.ToList();

user = new ClaimsPrincipal(new ClaimsIdentity(claims, "jwt"));

return claims;
return Task.FromResult(new AuthenticationState(user));
}
}
}
2 changes: 1 addition & 1 deletion API/ASSISTENTE.UI.Auth/Providers/Models/LoginDto.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
namespace ASSISTENTE.UI.Auth.Providers.Models;

public record LoginDto(string Email, string Password);
public sealed record LoginDto(string Email, string Password);
2 changes: 1 addition & 1 deletion API/ASSISTENTE.UI.Auth/Providers/Models/RedirectDto.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
namespace ASSISTENTE.UI.Auth.Providers.Models;

public record RedirectDto(string AccessToken, string RefreshToken);
public sealed record RedirectDto(string AccessToken, string RefreshToken);
2 changes: 1 addition & 1 deletion API/ASSISTENTE.UI.Auth/Providers/Models/RegisterDto.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
namespace ASSISTENTE.UI.Auth.Providers.Models;

public record RegisterDto(string Username, string Email, string Password);
public sealed record RegisterDto(string Username, string Email, string Password);

0 comments on commit b794aaa

Please sign in to comment.