Skip to content

Commit 9d5e41d

Browse files
committed
WASM Login & Validation & Logout Flows
1 parent 77ce23d commit 9d5e41d

File tree

18 files changed

+254
-138
lines changed

18 files changed

+254
-138
lines changed

samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Program.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@
6969
app.UseHttpsRedirection();
7070
app.UseCors("WasmSample");
7171

72+
app.UseUltimateAuthServer();
73+
app.UseAuthentication();
74+
app.UseAuthorization();
7275
app.UseAntiforgery();
7376

7477
app.MapUAuthEndpoints();

samples/blazor-standalone-wasm/CodeBeam.UltimateAuth.Sample.BlazorStandaloneWasm/Pages/Home.razor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@page "/"
2+
@page "/login"
23
@using CodeBeam.UltimateAuth.Client.Diagnostics
34
@using CodeBeam.UltimateAuth.Core.Runtime
45
@inject IHttpClientFactory HttpClientFactory

src/CodeBeam.UltimateAuth.Client/Abstractions/IBrowserPostClient.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ public interface IBrowserPostClient
1919
/// </summary>
2020
/// <param name="endpoint"></param>
2121
/// <returns></returns>
22-
Task<BrowserPostResult> BackgroundPostAsync(string endpoint);
22+
Task<BrowserPostResult> FetchPostAsync(string endpoint);
2323

24-
Task<BrowserPostJsonResult<T>> BackgroundPostJsonAsync<T>(string url);
24+
Task<BrowserPostJsonResult<T>> FetchPostJsonAsync<T>(string url);
2525
}
2626
}

src/CodeBeam.UltimateAuth.Client/Components/UALoginForm.razor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
@using Microsoft.Extensions.Options
55
@inject IJSRuntime JS
66
@inject IOptions<UAuthClientOptions> Options
7+
@inject NavigationManager Navigation
78

89
<form @ref="_form" method="post" action="@ResolvedEndpoint">
910
<input type="hidden" name="Identifier" value="@Identifier" />

src/CodeBeam.UltimateAuth.Client/Components/UALoginForm.razor.cs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ public partial class UALoginForm
1515
[Parameter]
1616
public string? Endpoint { get; set; } = "/auth/login";
1717

18+
[Parameter]
19+
public string? ReturnUrl { get; set; }
20+
1821
[Parameter]
1922
public RenderFragment? ChildContent { get; set; }
2023

@@ -23,14 +26,41 @@ public partial class UALoginForm
2326

2427
private ElementReference _form;
2528

26-
private string ResolvedEndpoint => string.IsNullOrWhiteSpace(Endpoint) ? UAuthUrlBuilder.Combine(Options.Value.Endpoints.Authority, "/auth/login") : UAuthUrlBuilder.Combine(Options.Value.Endpoints.Authority, Endpoint);
27-
2829
public async Task SubmitAsync()
2930
{
3031
if (_form.Context is null)
3132
throw new InvalidOperationException("Form is not yet rendered. Call SubmitAsync after OnAfterRender.");
3233

3334
await JS.InvokeVoidAsync("uauth.submitForm", _form);
3435
}
36+
37+
//private string ResolvedEndpoint => string.IsNullOrWhiteSpace(Endpoint) ? UAuthUrlBuilder.Combine(Options.Value.Endpoints.Authority, "/auth/login") : UAuthUrlBuilder.Combine(Options.Value.Endpoints.Authority, Endpoint);
38+
39+
private string ResolvedEndpoint
40+
{
41+
get
42+
{
43+
var loginPath = string.IsNullOrWhiteSpace(Endpoint)
44+
? Options.Value.Endpoints.Login
45+
: Endpoint;
46+
47+
var baseUrl = UAuthUrlBuilder.Combine(
48+
Options.Value.Endpoints.Authority,
49+
loginPath);
50+
51+
var returnUrl = EffectiveReturnUrl;
52+
53+
if (string.IsNullOrWhiteSpace(returnUrl))
54+
return baseUrl;
55+
56+
return $"{baseUrl}?returnUrl={Uri.EscapeDataString(returnUrl)}";
57+
}
58+
}
59+
60+
private string EffectiveReturnUrl =>
61+
!string.IsNullOrWhiteSpace(ReturnUrl)
62+
? ReturnUrl
63+
: Navigation.Uri;
64+
3565
}
3666
}

src/CodeBeam.UltimateAuth.Client/Infrastructure/BrowserPostClient.cs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,35 @@ public BrowserPostClient(IJSRuntime js)
1515

1616
public Task NavigatePostAsync(string endpoint, IDictionary<string, string>? data = null)
1717
{
18-
return _js.InvokeVoidAsync("uauth.post", endpoint, data).AsTask();
18+
return _js.InvokeVoidAsync("uauth.post", new
19+
{
20+
url = endpoint,
21+
mode = "navigate",
22+
data = data
23+
}).AsTask();
1924
}
2025

21-
public async Task<BrowserPostResult> BackgroundPostAsync(string endpoint)
26+
public async Task<BrowserPostResult> FetchPostAsync(string endpoint)
2227
{
23-
var result = await _js.InvokeAsync<BrowserPostResult>("uauth.refresh", endpoint);
28+
var result = await _js.InvokeAsync<BrowserPostResult>("uauth.post", new
29+
{
30+
url = endpoint,
31+
mode = "fetch",
32+
expectJson = false
33+
});
34+
2435
return result;
2536
}
2637

27-
public async Task<BrowserPostJsonResult<T>> BackgroundPostJsonAsync<T>(string url)
38+
public async Task<BrowserPostJsonResult<T>> FetchPostJsonAsync<T>(string endpoint)
2839
{
29-
var result = await _js.InvokeAsync<BrowserPostJsonResult<T>>("uauth.validate", url);
40+
var result = await _js.InvokeAsync<BrowserPostJsonResult<T>>("uauth.post", new
41+
{
42+
url = endpoint,
43+
mode = "fetch",
44+
expectJson = true
45+
});
46+
3047
return result;
3148
}
3249

src/CodeBeam.UltimateAuth.Client/Options/UAuthClientProfileDetector.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ public UAuthClientProfile Detect(IServiceProvider sp)
1313
if (AppDomain.CurrentDomain.GetAssemblies().Any(a => a.GetName().Name == "Microsoft.AspNetCore.Components.WebAssembly"))
1414
return UAuthClientProfile.BlazorWasm;
1515

16-
//if (sp.GetService<Microsoft.AspNetCore.Components.Server.Circuits.CircuitHandler>() is not null)
17-
// return UAuthClientProfile.BlazorServer;
16+
// Warning: This detection method may not be 100% reliable in all hosting scenarios.
17+
if (AppDomain.CurrentDomain.GetAssemblies().Any(a => a.GetName().Name == "Microsoft.AspNetCore.Components.Server"))
18+
{
19+
return UAuthClientProfile.BlazorServer;
20+
}
1821

19-
//if (sp.GetService<Microsoft.AspNetCore.Mvc.Infrastructure.IActionContextAccessor>() is not null)
20-
// return UAuthClientProfile.Mvc;
21-
22-
return UAuthClientProfile.NotSpecified;
22+
// Default to WebServer profile for other ASP.NET Core scenarios such as MVC, Razor Pages, minimal APIs, etc.
23+
// NotSpecified should only be used when user explicitly sets it. (For example in unit tests)
24+
return UAuthClientProfile.WebServer;
2325
}
2426
}
2527
}

src/CodeBeam.UltimateAuth.Client/Services/UAuthClient.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public async Task<RefreshResult> RefreshAsync(bool isAuto = false)
5050
}
5151

5252
var url = UAuthUrlBuilder.Combine(_options.Endpoints.Authority, _options.Endpoints.Refresh);
53-
var result = await _post.BackgroundPostAsync(url);
53+
var result = await _post.FetchPostAsync(url);
5454
var refreshOutcome = RefreshOutcomeParser.Parse(result.RefreshOutcome);
5555
switch (refreshOutcome)
5656
{
@@ -85,7 +85,7 @@ public async Task ReauthAsync()
8585
public async Task<AuthValidationResult> ValidateAsync()
8686
{
8787
var url = UAuthUrlBuilder.Combine(_options.Endpoints.Authority, _options.Endpoints.Validate);
88-
var result = await _post.BackgroundPostJsonAsync<AuthValidationResult>(url);
88+
var result = await _post.FetchPostJsonAsync<AuthValidationResult>(url);
8989

9090
if (result.Body is null)
9191
return new AuthValidationResult { IsValid = false, State = "transport" };

src/CodeBeam.UltimateAuth.Client/wwwroot/uauth.js

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,50 +7,48 @@
77
};
88

99
window.uauth = {
10-
post: function (action, data) {
11-
const form = document.createElement("form");
12-
form.method = "POST";
13-
form.action = action;
14-
15-
if (data) {
16-
for (const key in data) {
17-
const input = document.createElement("input");
18-
input.type = "hidden";
19-
input.name = key;
20-
input.value = data[key];
21-
form.appendChild(input);
10+
post: async function (options) {
11+
const {
12+
url,
13+
mode,
14+
data,
15+
expectJson
16+
} = options;
17+
18+
if (mode === "navigate") {
19+
const form = document.createElement("form");
20+
form.method = "POST";
21+
form.action = url;
22+
23+
if (data) {
24+
for (const key in data) {
25+
const input = document.createElement("input");
26+
input.type = "hidden";
27+
input.name = key;
28+
input.value = data[key];
29+
form.appendChild(input);
30+
}
2231
}
23-
}
24-
25-
document.body.appendChild(form);
26-
form.submit();
27-
},
28-
29-
refresh: async function (action) {
30-
const response = await fetch(action, {
31-
method: "POST",
32-
credentials: "include"
33-
});
3432

35-
return {
36-
ok: response.ok,
37-
status: response.status,
38-
refreshOutcome: response.headers.get("X-UAuth-Refresh")
39-
};
40-
},
33+
document.body.appendChild(form);
34+
form.submit();
35+
return null;
36+
}
4137

42-
validate: async function (action) {
43-
const response = await fetch(action, {
38+
const response = await fetch(url, {
4439
method: "POST",
4540
credentials: "include"
4641
});
4742

4843
let body = null;
49-
try { body = await response.json(); } catch { }
44+
if (expectJson) {
45+
try { body = await response.json(); } catch { }
46+
}
5047

5148
return {
5249
ok: response.ok,
5350
status: response.status,
51+
refreshOutcome: response.headers.get("X-UAuth-Refresh"),
5452
body: body
5553
};
5654
}

src/CodeBeam.UltimateAuth.Core/Options/UAuthClientProfile.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ public enum UAuthClientProfile
66
BlazorWasm,
77
BlazorServer,
88
Maui,
9-
Mvc,
9+
WebServer,
1010
Api
1111
}
1212
}

0 commit comments

Comments
 (0)