Skip to content

Commit

Permalink
Add logout feature
Browse files Browse the repository at this point in the history
  • Loading branch information
oveldman committed Apr 12, 2024
1 parent 749573c commit 89b105e
Show file tree
Hide file tree
Showing 20 changed files with 229 additions and 14 deletions.
6 changes: 6 additions & 0 deletions sources/ClientSdk.Grpc/Protos/authentication.proto
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
syntax = "proto3";

import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";

option csharp_namespace = "Server.Presentation.Grpc.Authentication.V1";
Expand All @@ -8,6 +9,7 @@ package authentication.v1;

service Authentication {
rpc Login (LoginRequest) returns (LoginResponse);
rpc Logout (google.protobuf.Empty) returns (LogoutResponse);
rpc Register (RegisterRequest) returns (RegisterResponse);
rpc TokenRefresh (TokenRefreshRequest) returns (LoginResponse);
}
Expand All @@ -25,6 +27,10 @@ message LoginResponse {
string refreshToken = 4;
}

message LogoutResponse {
bool isSuccess = 1;
}

message RegisterRequest {
string email = 1;
string password = 2;
Expand Down
1 change: 1 addition & 0 deletions sources/Clients.Admin.BFF/Clients.Admin.BFF.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
<PackageReference Include="Swashbuckle.AspNetCore" />
</ItemGroup>
Expand Down
26 changes: 26 additions & 0 deletions sources/Clients.Admin.BFF/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System.Text;
using MadWorldNL.Clients.Identity.Api.Shared.Builders;
using MadWorldNL.Clients.Identity.Api.Shared.Endpoints;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;

var builder = WebApplication.CreateBuilder(args);

Expand All @@ -10,6 +13,26 @@

builder.AddIdentity();

builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JwtSettings:Key"]!))
};
});

builder.Services.AddAuthorization();

const string madWorldOrigins = "_myAllowSpecificOrigins";
builder.Services.AddCors(options =>
{
Expand All @@ -34,6 +57,9 @@
app.UseHttpsRedirection();
app.AddIdentityEndpoints();

app.UseAuthentication();
app.UseAuthorization();

app.UseCors(madWorldOrigins);

app.Run();
3 changes: 3 additions & 0 deletions sources/Clients.Admin.BFF/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
"Audience": "https://admin.mad-world.nl",
"Host": " http://localhost:5266"
},
"JwtSettings": {
"Key": "mSWX4ctFHyPAPYddRzgVETAUEj3oJE2cNCPfhbyW9K5M4rXYjR"
},
"Cors": {
"AllowedOrigins": [
"https://localhost:7222"
Expand Down
3 changes: 3 additions & 0 deletions sources/Clients.Admin.BFF/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
"Audience": "https://admin.mad-world.nl",
"Host": " http://localhost:5266"
},
"JwtSettings": {
"Key": "Empty"
},
"Cors": {
"AllowedOrigins": [
"https://localhost:7222"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace MadWorldNL.Clients.Identity.Api.Contracts.Authentications;

public class LogoutProxyResponse
{
public bool IsSuccess { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,10 @@ private static void AddAuthenticationEndpoints(this WebApplication app)
([FromBody] LoginProxyRequest request, [FromServices] AuthenticationService authenticationService) =>
authenticationService.AuthenticateAsync(request))
.WithName("Login");

authenticationEndpoints.MapGet("/Logout", ([FromServices] AuthenticationService authenticationService) =>
authenticationService.LogoutAsync())
.RequireAuthorization()
.WithName("Logout");
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Google.Protobuf.WellKnownTypes;
using MadWorldNL.Clients.Identity.Api.Contracts.Authentications;
using MadWorldNL.Clients.Identity.Api.Shared.Settings;
using Microsoft.Extensions.Options;
Expand Down Expand Up @@ -30,4 +31,14 @@ public async Task<LoginProxyResponse> AuthenticateAsync(LoginProxyRequest proxyR
RefreshToken = response.RefreshToken
};
}

public async Task<LogoutProxyResponse> LogoutAsync()
{
var response = await client.LogoutAsync(new Empty());

return new LogoutProxyResponse()
{
IsSuccess = response.IsSuccess
};
}
}
29 changes: 29 additions & 0 deletions sources/Clients.Identity.Blazor.Shared/Accounts/AccountManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using MadWorldNL.Clients.Identity.Api.Contracts.Authentications;
using MadWorldNL.Clients.Identity.Blazor.Shared.Authentications;
using Microsoft.AspNetCore.Components.Authorization;

namespace MadWorldNL.Clients.Identity.Blazor.Shared.Accounts;

public class AccountManager : IAccountManager
{
private readonly IAccountService _accountService;
private readonly IAuthenticationStorage _authenticationStorage;
private readonly AuthenticationStateProvider _authenticationStateProvider;

public AccountManager(
IAccountService accountService,
IAuthenticationStorage authenticationStorage,
AuthenticationStateProvider authenticationStateProvider)
{
_accountService = accountService;
_authenticationStorage = authenticationStorage;
_authenticationStateProvider = authenticationStateProvider;
}

public async Task LogoutAsync()
{
await _accountService.LogoutAsync();
await _authenticationStorage.SetAccessTokenAsync(new LoginProxyResponse());
await _authenticationStateProvider.GetAuthenticationStateAsync();
}
}
23 changes: 23 additions & 0 deletions sources/Clients.Identity.Blazor.Shared/Accounts/AccountService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Net.Http.Json;
using MadWorldNL.Clients.Identity.Api.Contracts.Authentications;
using MadWorldNL.Clients.Identity.Blazor.Shared.Settings;

namespace MadWorldNL.Clients.Identity.Blazor.Shared.Accounts;

public class AccountService : IAccountService
{
private const string Endpoint = "Authentication";

private readonly HttpClient _httpClient;

public AccountService(IHttpClientFactory clientFactory)
{
_httpClient = clientFactory.CreateClient(ApiTypes.Identity);
}

public async Task<LogoutProxyResponse> LogoutAsync()
{
return await _httpClient.GetFromJsonAsync<LogoutProxyResponse>($"{Endpoint}/Logout")
?? new LogoutProxyResponse();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace MadWorldNL.Clients.Identity.Blazor.Shared.Accounts;

public interface IAccountManager
{
Task LogoutAsync();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using MadWorldNL.Clients.Identity.Api.Contracts.Authentications;

namespace MadWorldNL.Clients.Identity.Blazor.Shared.Accounts;

public interface IAccountService
{
Task<LogoutProxyResponse> LogoutAsync();
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ public async Task<LoginProxyResponse> LoginAsync(LoginProxyRequest request)
return response;
}

public async Task LogoutAsync()
public async Task<LoginProxyResponse> GetActiveTokenFromSession()
{
await _authenticationStorage.SetAccessTokenAsync(new LoginProxyResponse());
await _authenticationStateProvider.GetAuthenticationStateAsync();
return await _authenticationStorage.GetAccessTokenAsync();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ public class AuthenticationService : IAuthenticationService
{
private const string Endpoint = "Authentication";

private readonly HttpClient _client;
private readonly HttpClient _httpClient;
public AuthenticationService(IHttpClientFactory clientFactory)
{
_client = clientFactory.CreateClient(ApiTypes.AnonymousIdentity);
_httpClient = clientFactory.CreateClient(ApiTypes.AnonymousIdentity);
}

public async Task<LoginProxyResponse> LoginAsync(LoginProxyRequest request)
{
var response = await _client.PostAsJsonAsync($"{Endpoint}/Login", request);
return await response.Content.ReadFromJsonAsync<LoginProxyResponse>() ?? new LoginProxyResponse();
var response = await _httpClient.PostAsJsonAsync($"{Endpoint}/Login", request);
return await response.Content.ReadFromJsonAsync<LoginProxyResponse>()
?? new LoginProxyResponse();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ namespace MadWorldNL.Clients.Identity.Blazor.Shared.Authentications;
public interface IAuthenticationManager
{
Task<LoginProxyResponse> LoginAsync(LoginProxyRequest request);
Task LogoutAsync();
Task<LoginProxyResponse> GetActiveTokenFromSession();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System.Net;
using System.Net.Http.Headers;

namespace MadWorldNL.Clients.Identity.Blazor.Shared.Authentications;

public class MyHttpMessageHandler : DelegatingHandler
{
private readonly IAuthenticationManager _authenticationManager;

public MyHttpMessageHandler(IAuthenticationManager authenticationManager)
{
_authenticationManager = authenticationManager;
}

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
try
{
await AddAuthorizationHeader(request);
return await base.SendAsync(request, cancellationToken);
}
catch (RefreshTokenInvalidException)
{
return CreateExceptionMessage();
}
}

protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)
{
try
{
AddAuthorizationHeader(request).Wait(cancellationToken);
return base.Send(request, cancellationToken);
}
catch (RefreshTokenInvalidException)
{
return CreateExceptionMessage();
}
}

private async Task AddAuthorizationHeader(HttpRequestMessage request)
{
var accessToken = await _authenticationManager.GetActiveTokenFromSession();

if (!accessToken.IsSuccess)
{
throw new RefreshTokenInvalidException();
}

AddBearerToken(request, accessToken.AccessToken);
}

private static void AddBearerToken(HttpRequestMessage request, string token)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
}

private static HttpResponseMessage CreateExceptionMessage()
{
return new HttpResponseMessage()
{
StatusCode = HttpStatusCode.Unauthorized,
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace MadWorldNL.Clients.Identity.Blazor.Shared.Authentications;

public class RefreshTokenInvalidException : Exception
{

}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using MadWorldNL.Clients.Identity.Blazor.Shared.Accounts;
using MadWorldNL.Clients.Identity.Blazor.Shared.Authentications;
using MadWorldNL.Clients.Identity.Blazor.Shared.Settings;
using Microsoft.AspNetCore.Components.Authorization;
Expand All @@ -18,9 +19,14 @@ public static void AddIdentity(this WebAssemblyHostBuilder builder)
builder.Services.AddOptions<IdentitySettings>()
.Bind(builder.Configuration.GetSection(IdentitySettings.Entry));

builder.Services.AddScoped<MyHttpMessageHandler>();

builder.Services.AddScoped<IAuthenticationManager, AuthenticationManager>();
builder.Services.AddScoped<IAuthenticationService, AuthenticationService>();
builder.Services.AddScoped<IAuthenticationStorage, AuthenticationLocalStorage>();

builder.Services.AddScoped<IAccountManager, AccountManager>();
builder.Services.AddScoped<IAccountService, AccountService>();

builder.AddIdentityClients();
}
Expand All @@ -32,11 +38,11 @@ private static void AddIdentityClients(this WebAssemblyHostBuilder builder)
var identitySettingsOption = serviceProvider.GetService<IOptions<IdentitySettings>>()!;
client.BaseAddress = new Uri(identitySettingsOption.Value.Host!);
});

builder.Services.AddHttpClient(ApiTypes.Identity, (serviceProvider, client) =>
{
var identitySettingsOption = serviceProvider.GetService<IOptions<IdentitySettings>>()!;
client.BaseAddress = new Uri(identitySettingsOption.Value.Host!);
});
}).AddHttpMessageHandler<MyHttpMessageHandler>();
}
}
8 changes: 4 additions & 4 deletions sources/Clients.Identity.Blazor.Shared/Pages/Logout.razor
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
@page "/Logout"
@using MadWorldNL.Clients.Identity.Blazor.Shared.Authentications
@using MadWorldNL.Clients.Identity.Blazor.Shared.Accounts

<h3>You are logged out now!</h3>

@code {
[Inject] public IAuthenticationManager AuthenticationManager { get; set; } = null!;
[Inject] public IAccountManager AccountManager { get; set; } = null!;

protected override async Task OnInitializedAsync()
{
await AuthenticationManager.LogoutAsync();
await AccountManager.LogoutAsync();
}

}
Loading

0 comments on commit 89b105e

Please sign in to comment.