Skip to content

Commit 7f941a5

Browse files
authored
UAuthHub Sample Improvement (#25)
* Interactive Login Enhancement * Some WASM TryPkceComplete Arrangements * More Fixes * Completed PKCE * Code Refactoring * More Code Refactoring * UAuthUserAgentParser and Complete DeviceContext Implementation on WASM * Fix PKCE Client Provile & RedirectUri Mismatch * Added PKCE Tests * Little Sample Change
1 parent 848fae1 commit 7f941a5

119 files changed

Lines changed: 2925 additions & 697 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Components/App.razor

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
77
<base href="/" />
88
<ResourcePreloader />
9-
@* <link rel="stylesheet" href="@Assets["lib/bootstrap/dist/css/bootstrap.min.css"]" /> *@
109
<link rel="stylesheet" href="@Assets["app.css"]" />
11-
<link rel="stylesheet" href="@Assets["UltimateAuth.Sample.UAuthHub.styles.css"]" />
1210
<ImportMap />
13-
<link rel="icon" type="image/png" href="~/UltimateAuth-Logo.png" />
11+
<link rel="icon" type="image/png" href="UltimateAuth-Logo.png" />
1412

1513
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
1614
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
Lines changed: 97 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,101 @@
1-
@using CodeBeam.UltimateAuth.Core.Abstractions
2-
@using CodeBeam.UltimateAuth.Server.Infrastructure
3-
@inherits LayoutComponentBase
1+
@inherits UAuthHubLayoutComponentBase
2+
@inject IUAuthClient UAuthClient
3+
@inject ISnackbar Snackbar
4+
@inject NavigationManager Nav
45

5-
@Body
6+
@if (!IsHubAuthorized)
7+
{
8+
<MudAppBar Class="uauth-blur" Color="Color.Transparent" Dense="true" Elevation="0">
9+
<UAuthLogo />
10+
<MudText Class="ml-2 cursor-pointer" Style="user-select: none" @onclick="@(() => Nav.NavigateTo("/home", true))"><b>UltimateAuth</b></MudText>
11+
<MudDivider Class="ml-3 mr-1" Vertical="true" />
12+
<MudText Class="ml-2" Style="line-height: 14px" Typo="Typo.subtitle2">UAuthHub Sample</MudText>
613

7-
<div id="blazor-error-ui" data-nosnippet>
14+
<MudSpacer />
15+
16+
<MudIconButton Icon="@(DarkModeManager.IsDarkMode? Icons.Material.Filled.LightMode : Icons.Material.Filled.DarkMode)" OnClick="@(() => DarkModeManager.ToggleAsync())" />
17+
</MudAppBar>
18+
19+
<MudPage Class="d-flex align-center justify-center" FullScreen="FullScreen.FullWithoutAppbar" Column="1" Row="1">
20+
<MudPaper Class="pa-8" Elevation="4" Style="max-width: 520px; width: 100%;">
21+
<MudStack Spacing="3" AlignItems="AlignItems.Center">
22+
<UAuthLogo Size="72" />
23+
24+
<MudText Typo="Typo.h5"><b>Access Denied</b></MudText>
25+
26+
<MudText Typo="Typo.body2" Align="Align.Center">
27+
This page cannot be accessed directly.
28+
UAuthHub login flows can only be initiated by an authorized client application.
29+
</MudText>
30+
31+
<MudDivider Class="my-2" />
32+
33+
<MudText Typo="Typo.caption" Color="Color.Secondary">
34+
UltimateAuth protects this resource based on your session and permissions.
35+
</MudText>
36+
</MudStack>
37+
</MudPaper>
38+
</MudPage>
39+
return;
40+
}
41+
42+
<MudLayout>
43+
<MudAppBar Class="uauth-blur" Color="Color.Transparent" Dense="true" Elevation="0">
44+
<UAuthLogo />
45+
<MudText Class="ml-2 cursor-pointer" Style="user-select: none" @onclick="@(() => Nav.NavigateTo("/home", true))"><b>UltimateAuth</b></MudText>
46+
<MudDivider Class="ml-3 mr-1" Vertical="true" />
47+
<MudText Class="ml-2" Style="line-height: 14px" Typo="Typo.subtitle2">UAuthHub Sample</MudText>
48+
49+
<MudSpacer />
50+
51+
<MudIconButton Icon="@(DarkModeManager.IsDarkMode? Icons.Material.Filled.LightMode : Icons.Material.Filled.DarkMode)" OnClick="@(() => DarkModeManager.ToggleAsync())" />
52+
53+
<UAuthStateView>
54+
<Authorized Context="state">
55+
<MudMenu PopoverClass="mud-theme-primary uauth-menu-popover" AnchorOrigin="Origin.BottomRight" TransformOrigin="Origin.TopRight">
56+
<ActivatorContent>
57+
<div @onclick="@(() => context.ToggleAsync())">
58+
<MudBadge Overlap="true" Dot="true" Color="@GetBadgeColor()">
59+
<MudAvatar Class="cursor-pointer mud-ripple" Variant="Variant.Filled" Color="Color.Primary">
60+
<MudText Typo="Typo.subtitle2">@((state.Identity?.DisplayName ?? "?").Trim() is var n ? (n.Length >= 2 ? n[..2] : n[..1]) : "?")</MudText>
61+
</MudAvatar>
62+
</MudBadge>
63+
</div>
64+
</ActivatorContent>
65+
66+
<ChildContent>
67+
<MudText Class="px-4 py-2"><b>@state.Identity?.DisplayName</b></MudText>
68+
<MudText Class="px-4" Typo="Typo.subtitle2">@string.Join(", ", state.Claims.Roles)</MudText>
69+
70+
<MudDivider />
71+
72+
<MudMenuItem Icon="@Icons.Material.Filled.Refresh" IconColor="Color.Secondary" Label="Refresh Session" OnClick="@Refresh" />
73+
<MudMenuItem Icon="@Icons.Material.Filled.VerifiedUser" IconColor="Color.Secondary" Label="Validate Session" OnClick="@Validate" />
74+
<MudMenuItem Icon="@Icons.Material.Filled.Logout" IconColor="Color.Secondary" Label="Logout" OnClick="@Logout" />
75+
76+
@if (state.Identity?.SessionState is not null && state.Identity.SessionState != SessionState.Active)
77+
{
78+
<MudDivider />
79+
<MudMenuItem Icon="@Icons.Material.Filled.Login" Label="Reauthenticate" OnClick="@GoToLoginWithReturn" />
80+
}
81+
</ChildContent>
82+
</MudMenu>
83+
</Authorized>
84+
85+
<NotAuthorized>
86+
<MudChip T="string" Style="width: fit-content" Text="Sign In" Color="Color.Primary" Variant="Variant.Filled" Class="cursor-pointer" OnClick="@HandleSignInClick" />
87+
</NotAuthorized>
88+
</UAuthStateView>
89+
</MudAppBar>
90+
91+
<MudMainContent>
92+
@Body
93+
</MudMainContent>
94+
</MudLayout>
95+
96+
97+
<div id="blazor-error-ui">
898
An unhandled error has occurred.
9-
<a href="." class="reload">Reload</a>
10-
<span class="dismiss">🗙</span>
99+
<a href="" class="reload">Reload</a>
100+
<a class="dismiss">🗙</a>
11101
</div>
Lines changed: 126 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,130 @@
1-
namespace CodeBeam.UltimateAuth.Sample.UAuthHub.Components.Layout
1+
using CodeBeam.UltimateAuth.Client;
2+
using CodeBeam.UltimateAuth.Client.Errors;
3+
using CodeBeam.UltimateAuth.Core.Contracts;
4+
using CodeBeam.UltimateAuth.Core.Domain;
5+
using CodeBeam.UltimateAuth.Core.Errors;
6+
using CodeBeam.UltimateAuth.Sample.UAuthHub.Infrastructure;
7+
using Microsoft.AspNetCore.Components;
8+
using MudBlazor;
9+
10+
namespace CodeBeam.UltimateAuth.Sample.UAuthHub.Components.Layout;
11+
12+
public partial class MainLayout
213
{
3-
public partial class MainLayout
14+
[CascadingParameter]
15+
public UAuthState UAuth { get; set; } = default!;
16+
17+
[CascadingParameter]
18+
public DarkModeManager DarkModeManager { get; set; } = default!;
19+
20+
private async Task Refresh()
421
{
5-
22+
await UAuthClient.Flows.RefreshAsync();
23+
}
24+
25+
private async Task Logout()
26+
{
27+
await UAuthClient.Flows.LogoutAsync();
28+
}
29+
30+
private Color GetBadgeColor()
31+
{
32+
if (UAuth is null || !UAuth.IsAuthenticated)
33+
return Color.Error;
34+
35+
if (UAuth.IsStale)
36+
return Color.Warning;
37+
38+
var state = UAuth.Identity?.SessionState;
39+
40+
if (state is null || state == SessionState.Active)
41+
return Color.Success;
42+
43+
if (state == SessionState.Invalid)
44+
return Color.Error;
45+
46+
return Color.Warning;
47+
}
48+
49+
private void HandleSignInClick()
50+
{
51+
var uri = Nav.ToAbsoluteUri(Nav.Uri);
52+
53+
if (uri.AbsolutePath.EndsWith("/login", StringComparison.OrdinalIgnoreCase))
54+
{
55+
Nav.NavigateTo("/login?focus=1", replace: true, forceLoad: true);
56+
return;
57+
}
58+
59+
GoToLoginWithReturn();
60+
}
61+
62+
private async Task Validate()
63+
{
64+
try
65+
{
66+
var result = await UAuthClient.Flows.ValidateAsync();
67+
68+
if (result.IsValid)
69+
{
70+
if (result.Snapshot?.Identity.UserStatus == UserStatus.SelfSuspended)
71+
{
72+
Snackbar.Add("Your account is suspended by you.", Severity.Warning);
73+
return;
74+
}
75+
Snackbar.Add($"Session active • Tenant: {result.Snapshot?.Identity?.Tenant.Value} • User: {result.Snapshot?.Identity?.PrimaryUserName}", Severity.Success);
76+
}
77+
else
78+
{
79+
switch (result.State)
80+
{
81+
case SessionState.Expired:
82+
Snackbar.Add("Session expired. Please sign in again.", Severity.Warning);
83+
break;
84+
85+
case SessionState.DeviceMismatch:
86+
Snackbar.Add("Session invalid for this device.", Severity.Error);
87+
break;
88+
89+
default:
90+
Snackbar.Add($"Session state: {result.State}", Severity.Error);
91+
break;
92+
}
93+
}
94+
}
95+
catch (UAuthTransportException)
96+
{
97+
Snackbar.Add("Network error.", Severity.Error);
98+
}
99+
catch (UAuthProtocolException)
100+
{
101+
Snackbar.Add("Invalid response.", Severity.Error);
102+
}
103+
catch (UAuthException ex)
104+
{
105+
Snackbar.Add($"UAuth error: {ex.Message}", Severity.Error);
106+
}
107+
catch (Exception ex)
108+
{
109+
Snackbar.Add($"Unexpected error: {ex.Message}", Severity.Error);
110+
}
111+
}
112+
113+
private void GoToLoginWithReturn()
114+
{
115+
var uri = Nav.ToAbsoluteUri(Nav.Uri);
116+
117+
if (uri.AbsolutePath.EndsWith("/login", StringComparison.OrdinalIgnoreCase))
118+
{
119+
Nav.NavigateTo("/login", replace: true);
120+
return;
121+
}
122+
123+
var current = Nav.ToBaseRelativePath(uri.ToString());
124+
if (string.IsNullOrWhiteSpace(current))
125+
current = "home";
126+
127+
var returnUrl = Uri.EscapeDataString("/" + current.TrimStart('/'));
128+
Nav.NavigateTo($"/login?returnUrl={returnUrl}", replace: true);
6129
}
7130
}

0 commit comments

Comments
 (0)