Skip to content

Commit a4bf89c

Browse files
committed
Finalize First Working Login Flow on Blazor Server
1 parent ae28b48 commit a4bf89c

File tree

18 files changed

+340
-75
lines changed

18 files changed

+340
-75
lines changed
Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
@page "/"
2+
@inject IUAuthFlowService<UserId> FlowService
3+
@inject ISnackbar Snackbar
4+
@inject IHttpClientFactory Http
25

36
<div class="uauth-page d-flex align-center justify-center">
47
<MudStack Class="uauth-stack">
5-
<MudText Typo="Typo.h4">Welcome to UltimateAuth!</MudText>
6-
<MudStack>
7-
<MudTextField @bind-Value="@_username" Variant="Variant.Outlined" Label="Username" />
8-
<MudPasswordField @bind-Value="@_password" Variant="Variant.Outlined" Label="Password" />
9-
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="LoginAsync">Login</MudButton>
10-
</MudStack>
11-
8+
<form method="post" action="/auth/login">
9+
<MudText Typo="Typo.h4">Welcome to UltimateAuth!</MudText>
10+
<!-- Hidden fields -->
11+
<input type="hidden" name="Identifier" value="@_username" />
12+
<input type="hidden" name="Secret" value="@_password" />
13+
14+
<!-- UI -->
15+
<MudTextField @bind-Value="@_username" Variant="Variant.Outlined" Label="Username" Immediate="true" />
16+
<MudPasswordField @bind-Value="@_password" Variant="Variant.Outlined" Label="Password" Immediate="true" />
17+
18+
<MudButton Variant="Variant.Filled" Color="Color.Primary" ButtonType="ButtonType.Submit">Login</MudButton>
19+
</form>
1220
</MudStack>
13-
1421
</div>

samples/blazor-server/UltimateAuth.BlazorServer/Components/Pages/Home.razor.cs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
namespace UltimateAuth.BlazorServer.Components.Pages
1+
using CodeBeam.UltimateAuth.Core.Contracts;
2+
using Microsoft.AspNetCore.Authentication.Cookies;
3+
using Microsoft.AspNetCore.Http;
4+
using MudBlazor;
5+
6+
namespace UltimateAuth.BlazorServer.Components.Pages
27
{
38
public partial class Home
49
{
@@ -8,6 +13,35 @@ public partial class Home
813
private async Task LoginAsync()
914
{
1015

16+
try
17+
{
18+
//var result = await FlowService.LoginAsync(new LoginRequest
19+
//{
20+
// Identifier = _username!,
21+
// Secret = _password!
22+
//});
23+
var client = Http.CreateClient();
24+
var result = await client.PostAsJsonAsync(
25+
"https://localhost:7213/auth/login",
26+
new LoginRequest
27+
{
28+
Identifier = _username!,
29+
Secret = _password!
30+
});
31+
32+
33+
if (!result.IsSuccessStatusCode)
34+
{
35+
Snackbar.Add("Login failed.", Severity.Info);
36+
return;
37+
}
38+
39+
Snackbar.Add("Successfully logged in!", Severity.Success);
40+
}
41+
catch (Exception ex)
42+
{
43+
Snackbar.Add(ex.ToString(), Severity.Error);
44+
}
1145
}
1246
}
1347
}

samples/blazor-server/UltimateAuth.BlazorServer/Components/_Imports.razor

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,8 @@
99
@using UltimateAuth.BlazorServer
1010
@using UltimateAuth.BlazorServer.Components
1111

12+
@using CodeBeam.UltimateAuth.Core.Abstractions
13+
@using CodeBeam.UltimateAuth.Core.Domain
14+
1215
@using MudBlazor
1316
@using MudExtensions

samples/blazor-server/UltimateAuth.BlazorServer/Program.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using CodeBeam.UltimateAuth.Server.Extensions;
44
using CodeBeam.UltimateAuth.Sessions.InMemory;
55
using CodeBeam.UltimateAuth.Tokens.InMemory;
6+
using Microsoft.AspNetCore.Components;
67
using MudBlazor.Services;
78
using MudExtensions.Services;
89
using UltimateAuth.BlazorServer.Components;
@@ -19,19 +20,28 @@
1920
builder.Services.AddAuthentication();
2021
builder.Services.AddAuthorization();
2122

23+
builder.Services.AddHttpContextAccessor();
24+
2225

2326
builder.Services.AddUltimateAuthServer()
2427
.AddInMemoryCredentials()
2528
.AddUltimateAuthInMemorySessions()
2629
.AddUltimateAuthInMemoryTokens()
2730
.AddUltimateAuthArgon2();
2831

29-
3032
builder.Services.AddHttpClient("AuthApi", client =>
3133
{
32-
client.BaseAddress = new Uri(builder.Configuration["App:BaseUrl"]!);
34+
client.BaseAddress = new Uri("https://localhost:7213");
35+
})
36+
.ConfigurePrimaryHttpMessageHandler(() =>
37+
{
38+
return new HttpClientHandler
39+
{
40+
UseCookies = true
41+
};
3342
});
3443

44+
3545
var app = builder.Build();
3646

3747
// Configure the HTTP request pipeline.

src/CodeBeam.UltimateAuth.Core/Infrastructure/Authority/DefaultAuthAuthority.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ public sealed class DefaultAuthAuthority : IAuthAuthority
88
private readonly IEnumerable<IAuthorityInvariant> _invariants;
99
private readonly IEnumerable<IAuthorityPolicy> _policies;
1010

11+
public DefaultAuthAuthority(IEnumerable<IAuthorityInvariant> invariants, IEnumerable<IAuthorityPolicy> policies)
12+
{
13+
_invariants = invariants ?? Array.Empty<IAuthorityInvariant>();
14+
_policies = policies ?? Array.Empty<IAuthorityPolicy>();
15+
}
16+
1117
public AuthorizationResult Decide(AuthContext context)
1218
{
1319
// 1. Invariants

src/CodeBeam.UltimateAuth.Core/Infrastructure/UAuthRefreshTokenResolver.cs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,20 @@
33

44
namespace CodeBeam.UltimateAuth.Core.Infrastructure
55
{
6-
internal sealed class StoreRefreshTokenResolver<TUserId>
7-
: IRefreshTokenResolver<TUserId>
6+
public sealed class UAuthRefreshTokenResolver<TUserId> : IRefreshTokenResolver<TUserId>
87
{
98
private readonly ISessionStoreFactory _sessionStoreFactory;
109
private readonly ITokenStoreFactory _tokenStoreFactory;
1110
private readonly ITokenHasher _hasher;
1211

13-
public StoreRefreshTokenResolver(
14-
ISessionStoreFactory sessionStoreFactory,
15-
ITokenStoreFactory tokenStoreFactory,
16-
ITokenHasher hasher)
12+
public UAuthRefreshTokenResolver(ISessionStoreFactory sessionStoreFactory, ITokenStoreFactory tokenStoreFactory, ITokenHasher hasher)
1713
{
1814
_sessionStoreFactory = sessionStoreFactory;
1915
_tokenStoreFactory = tokenStoreFactory;
2016
_hasher = hasher;
2117
}
2218

23-
public async Task<ResolvedRefreshSession<TUserId>?> ResolveAsync(
24-
string? tenantId,
25-
string refreshToken,
26-
DateTimeOffset now,
27-
CancellationToken ct = default)
19+
public async Task<ResolvedRefreshSession<TUserId>?> ResolveAsync(string? tenantId, string refreshToken, DateTimeOffset now, CancellationToken ct = default)
2820
{
2921
var tokenHash = _hasher.Hash(refreshToken);
3022

src/CodeBeam.UltimateAuth.Server/Endpoints/DefaultLoginEndpointHandler.cs

Lines changed: 66 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using CodeBeam.UltimateAuth.Core.Contracts;
33
using CodeBeam.UltimateAuth.Server.Abstractions;
44
using CodeBeam.UltimateAuth.Server.Contracts;
5+
using CodeBeam.UltimateAuth.Server.Cookies;
56
using CodeBeam.UltimateAuth.Server.Endpoints;
67
using CodeBeam.UltimateAuth.Server.Extensions;
78
using CodeBeam.UltimateAuth.Server.MultiTenancy;
@@ -13,26 +14,39 @@ public sealed class DefaultLoginEndpointHandler<TUserId> : ILoginEndpointHandler
1314
private readonly IDeviceResolver _deviceResolver;
1415
private readonly ITenantResolver _tenantResolver;
1516
private readonly IClock _clock;
17+
private readonly IUAuthSessionCookieManager _cookieManager;
1618

1719
public DefaultLoginEndpointHandler(
1820
IUAuthFlowService<TUserId> flow,
1921
IDeviceResolver deviceResolver,
2022
ITenantResolver tenantResolver,
21-
IClock clock)
23+
IClock clock,
24+
IUAuthSessionCookieManager cookieManager)
2225
{
2326
_flow = flow;
2427
_deviceResolver = deviceResolver;
2528
_tenantResolver = tenantResolver;
2629
_clock = clock;
30+
_cookieManager = cookieManager;
2731
}
2832

2933
public async Task<IResult> LoginAsync(HttpContext ctx)
3034
{
31-
var request = await ctx.Request.ReadFromJsonAsync<LoginRequest>();
32-
if (request is null)
33-
return Results.BadRequest("Invalid login request.");
35+
if (!ctx.Request.HasFormContentType)
36+
return Results.BadRequest("Invalid content type.");
37+
38+
var form = await ctx.Request.ReadFormAsync();
39+
40+
var request = new LoginRequest
41+
{
42+
Identifier = form["Identifier"],
43+
Secret = form["Secret"]
44+
};
45+
46+
if (string.IsNullOrWhiteSpace(request.Identifier) ||
47+
string.IsNullOrWhiteSpace(request.Secret))
48+
return Results.Redirect("/login?error=invalid");
3449

35-
// Middleware should have already resolved the tenant
3650
var tenantCtx = ctx.GetTenantContext();
3751

3852
var flowRequest = request with
@@ -44,23 +58,54 @@ public async Task<IResult> LoginAsync(HttpContext ctx)
4458

4559
var result = await _flow.LoginAsync(flowRequest, ctx.RequestAborted);
4660

47-
return result.Status switch
48-
{
49-
LoginStatus.Success => Results.Ok(new LoginResponse
50-
{
51-
SessionId = result.SessionId,
52-
AccessToken = result.AccessToken,
53-
RefreshToken = result.RefreshToken
54-
}),
55-
56-
LoginStatus.RequiresContinuation => Results.Ok(new LoginResponse
57-
{
58-
Continuation = result.Continuation
59-
}),
61+
if (!result.IsSuccess)
62+
return Results.Redirect("/login?error=invalid");
6063

61-
LoginStatus.Failed => Results.Unauthorized(),
64+
_cookieManager.Issue(ctx, result.SessionId!.Value);
6265

63-
_ => Results.StatusCode(StatusCodes.Status500InternalServerError)
64-
};
66+
return Results.Redirect("/");
6567
}
68+
69+
//public async Task<IResult> LoginAsync(HttpContext ctx)
70+
//{
71+
// var request = await ctx.Request.ReadFromJsonAsync<LoginRequest>();
72+
// if (request is null)
73+
// return Results.BadRequest("Invalid login request.");
74+
75+
// // Middleware should have already resolved the tenant
76+
// var tenantCtx = ctx.GetTenantContext();
77+
78+
// var flowRequest = request with
79+
// {
80+
// TenantId = tenantCtx.TenantId,
81+
// At = _clock.UtcNow,
82+
// DeviceInfo = _deviceResolver.Resolve(ctx)
83+
// };
84+
85+
// var result = await _flow.LoginAsync(flowRequest, ctx.RequestAborted);
86+
87+
// if (result.IsSuccess)
88+
// {
89+
// _cookieManager.Issue(ctx, result.SessionId.Value);
90+
// }
91+
92+
// return result.Status switch
93+
// {
94+
// LoginStatus.Success => Results.Ok(new LoginResponse
95+
// {
96+
// SessionId = result.SessionId,
97+
// AccessToken = result.AccessToken,
98+
// RefreshToken = result.RefreshToken
99+
// }),
100+
101+
// LoginStatus.RequiresContinuation => Results.Ok(new LoginResponse
102+
// {
103+
// Continuation = result.Continuation
104+
// }),
105+
106+
// LoginStatus.Failed => Results.Unauthorized(),
107+
108+
// _ => Results.StatusCode(StatusCodes.Status500InternalServerError)
109+
// };
110+
//}
66111
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using CodeBeam.UltimateAuth.Core.Domain;
2+
using Microsoft.AspNetCore.Http;
3+
4+
namespace CodeBeam.UltimateAuth.Server.Endpoints
5+
{
6+
internal sealed class LoginEndpointHandlerBridge : ILoginEndpointHandler
7+
{
8+
private readonly DefaultLoginEndpointHandler<UserId> _inner;
9+
10+
public LoginEndpointHandlerBridge(DefaultLoginEndpointHandler<UserId> inner)
11+
{
12+
_inner = inner;
13+
}
14+
15+
public Task<IResult> LoginAsync(HttpContext ctx)
16+
=> _inner.LoginAsync(ctx);
17+
}
18+
19+
}

src/CodeBeam.UltimateAuth.Server/Endpoints/UAuthEndpointDefaultsMap.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,15 @@ namespace CodeBeam.UltimateAuth.Server.Endpoints
99
/// </summary>
1010
internal static class UAuthEndpointDefaultsMap
1111
{
12-
public static UAuthEndpointDefaults ForMode(UAuthMode mode)
12+
public static UAuthEndpointDefaults ForMode(UAuthMode? mode)
1313
{
14+
if (!mode.HasValue)
15+
{
16+
throw new InvalidOperationException(
17+
"UAuthMode must be resolved before endpoint mapping. " +
18+
"Ensure ClientProfile defaults are applied.");
19+
}
20+
1421
return mode switch
1522
{
1623
UAuthMode.PureOpaque => new UAuthEndpointDefaults

src/CodeBeam.UltimateAuth.Server/Endpoints/UAuthEndpointRegistrar.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public interface IAuthEndpointRegistrar
1111
void MapEndpoints(RouteGroupBuilder rootGroup, UAuthServerOptions options);
1212
}
1313

14+
// TODO: Add Scalar/Swagger integration
1415
public class UAuthEndpointRegistrar : IAuthEndpointRegistrar
1516
{
1617
public void MapEndpoints(RouteGroupBuilder rootGroup, UAuthServerOptions options)

0 commit comments

Comments
 (0)