Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions UltimateAuth.slnx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<Solution>
<Folder Name="/Apps/">
<Project Path="apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/CodeBeam.UltimateAuth.IdentityLab.csproj" />
</Folder>
<Folder Name="/Docs/">
<Project Path="docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/CodeBeam.UltimateAuth.Docs.Wasm.Client.csproj" Id="1976793c-a84c-4009-96c5-1fb8c7657c5e" />
<Project Path="docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.csproj" Id="629c8d74-d253-4ef2-9f26-47a405d9d7a6" />
Expand Down
24 changes: 24 additions & 0 deletions apps/identity-lab/CAPABILITY-AUDIT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Identity Lab Phase 1 capability audit

This audit records the public API boundary used by the three-screen Identity Lab application. Identity Lab is a live application that does not access an UltimateAuth store or persistence model directly.

| Screen / UI feature | UltimateAuth capability | Public API / type | Usable? | Ownership / decision |
|---|---|---|---|---|
| Landing: isolated playground copy and transition | None required | — | Yes | Identity Lab presentation. The environment uses the real in-memory UltimateAuth bundle. |
| Sign in | Flow-based login | `UAuthLoginForm`, `IUAuthClient.Flows`, `LoginRequest` | Yes | UltimateAuth owns validation, login, session establishment and errors. |
| Create account | User creation | `IUAuthClient.Users.CreateAsync(CreateUserRequest)` | Yes | UltimateAuth owns users and credentials. Identity Lab only collects input. |
| Remember me | No matching public semantic was found | — | No | Omitted rather than emulated. |
| GitHub login | No configured external-provider public flow in the inspected samples | — | No | Omitted rather than presenting a non-functional control. |
| Current identity and authorization state | Auth state snapshot | `UAuthPageBase.AuthState`, `UAuthStateView`, `UAuthAuthorize` | Yes | UltimateAuth-backed identity, tenant, roles and session state. |
| Device/session overview | Root → Chain → Session model | `IUAuthClient.Sessions.GetMyChainsAsync`, `SessionChainSummary` | Yes | A chain is labelled as a device/client context; it is not presented as a raw session. |
| Sign out the current session | Current-session logout | `IUAuthClient.Flows.LogoutAsync()` | Yes | The normal current-device action ends the current session without explicitly revoking its chain. |
| Log out another device | Device logout | `IUAuthClient.Flows.LogoutMyDeviceAsync(LogoutDeviceRequest)` | Yes | Used for non-current chains; UltimateAuth owns the logout and resulting chain/session state. |
| Log out other/all devices | Multi-device logout | `IUAuthClient.Flows.LogoutMyOtherDevicesAsync()`, `LogoutAllMyDevicesAsync()` | Yes, not surfaced in Phase 1 | Public APIs exist, but no corresponding approved Phase 1 control is present. |
| Explicit chain revocation | Chain revocation | `IUAuthClient.Sessions.RevokeMyChainAsync(SessionChainId)` | Yes, not surfaced | Reserved for a future advanced security experience; it is not disguised as everyday logout. |
| Validate session | Session flow validation | `IUAuthClient.Flows.ValidateAsync()` | Yes | UltimateAuth-backed. |
| Client profile/runtime | Client product metadata | `IUAuthClientProductInfoProvider.Get()` | Yes | UltimateAuth-backed. |
| Playground label and storage description | Environment metadata | app configuration | Yes | Identity Lab-owned presentation metadata. No security meaning. |
| All-user count/list | Admin query exists but should only appear after permission-aware product design | `IUAuthClient.Users.QueryAsync(UserQuery)` | Deferred | Not queried in Phase 1; avoids conflating the current-user dashboard with admin access. |
| Global active-session count | No unrestricted aggregate API identified | — | No | Omitted; current user's chain count is shown instead. |
| Authentication/security event feed | No public query API identified | — | No | Omitted; the landing preview describes capabilities rather than displaying fabricated events. |
| Playground creation, expiry and quotas | No Identity Lab persistence/service exists in Phase 1 | — | No | Omitted until genuine playground metadata storage is introduced. |
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MudBlazor" Version="9.10.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\bundle\CodeBeam.UltimateAuth.InMemory.Bundle\CodeBeam.UltimateAuth.InMemory.Bundle.csproj" />
<ProjectReference Include="..\..\..\src\client\CodeBeam.UltimateAuth.Client.Blazor\CodeBeam.UltimateAuth.Client.Blazor.csproj" />
<ProjectReference Include="..\..\..\samples\CodeBeam.UltimateAuth.Sample.Seed\CodeBeam.UltimateAuth.Sample.Seed.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<base href="/" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&amp;display=swap" />
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
<link href="app.css" rel="stylesheet" />
<HeadOutlet @rendermode="InteractiveServer" />
</head>
<body>
<Routes @rendermode="InteractiveServer" />
<script src="_framework/blazor.web.js"></script>
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
<script src="_content/CodeBeam.UltimateAuth.Client.Blazor/uauth.min.js"></script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@inherits LayoutComponentBase

<MudLayout>
<MudAppBar Elevation="0" Class="lab-appbar">
<a class="brand" href="/"><span class="brand-mark">U</span><strong>UltimateAuth</strong></a>
<MudSpacer />
<MudLink Href="/" Underline="Underline.None">Playground</MudLink>
<MudLink Href="https://ultimateauth.com" Target="_blank" Underline="Underline.None">Documentation</MudLink>
<MudLink Href="https://github.com/CodeBeamOrg/UltimateAuth" Target="_blank" Underline="Underline.None">GitHub ↗</MudLink>
</MudAppBar>
<MudMainContent>@Body</MudMainContent>
</MudLayout>

<div id="blazor-error-ui">An unexpected error occurred. <a href="">Reload</a></div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
@page "/access"
@page "/login"
@attribute [UAuthLoginPage]
@inherits UAuthPageBase
@inject IUAuthClient UAuthClient
@inject ISnackbar Snackbar

<PageTitle>Sign in · Identity Lab</PageTitle>
<main class="access-shell">
<div class="access-heading"><div class="eyebrow">ULTIMATEAUTH</div><h1>Identity Lab</h1><p>Explore authentication, authorization and session management<br />in a live application built with Blazor and UltimateAuth.</p></div>
<MudPaper Class="access-card" Elevation="0">
<div class="auth-tabs"><button class="@(!create ? "active" : null)" @onclick="() => create = false">Sign In</button><button class="@(create ? "active" : null)" @onclick="() => create = true">Create Account</button></div>
@if (!create)
{
<UAuthLoginForm Identifier="username" Secret="password" ReturnUrl="/dashboard" OnTryResult="HandleLogin">
<MudStack Spacing="2">
<MudTextField @bind-Value="username" Label="Username or email" Placeholder="you@example.com" Variant="Variant.Outlined" Required="true" />
<MudPasswordField @bind-Value="password" Label="Password" Placeholder="Your password" Variant="Variant.Outlined" Required="true" />

Check warning on line 18 in apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Access.razor

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (8.0.x)

Found markup element with unexpected name 'MudPasswordField'. If this is intended to be a component, add a @using directive for its namespace.

Check failure on line 18 in apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Access.razor

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (8.0.x)

The attribute names could not be inferred from bind attribute 'bind-Value'. Bind attributes should be of the form 'bind' or 'bind-value' along with their corresponding optional parameters like 'bind-value:event', 'bind:format' etc.

Check warning on line 18 in apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Access.razor

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (8.0.x)

Found markup element with unexpected name 'MudPasswordField'. If this is intended to be a component, add a @using directive for its namespace.

Check warning on line 18 in apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Access.razor

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (10.0.x)

Found markup element with unexpected name 'MudPasswordField'. If this is intended to be a component, add a @using directive for its namespace.

Check failure on line 18 in apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Access.razor

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (10.0.x)

The attribute names could not be inferred from bind attribute 'bind-Value'. Bind attributes should be of the form 'bind' or 'bind-value' along with their corresponding optional parameters like 'bind-value:event', 'bind:format' etc.

Check warning on line 18 in apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Access.razor

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (10.0.x)

Found markup element with unexpected name 'MudPasswordField'. If this is intended to be a component, add a @using directive for its namespace.

Check warning on line 18 in apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Access.razor

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (9.0.x)

Found markup element with unexpected name 'MudPasswordField'. If this is intended to be a component, add a @using directive for its namespace.

Check failure on line 18 in apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Access.razor

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (9.0.x)

The attribute names could not be inferred from bind attribute 'bind-Value'. Bind attributes should be of the form 'bind' or 'bind-value' along with their corresponding optional parameters like 'bind-value:event', 'bind:format' etc.

Check warning on line 18 in apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Access.razor

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (9.0.x)

Found markup element with unexpected name 'MudPasswordField'. If this is intended to be a component, add a @using directive for its namespace.
<MudButton Class="auth-submit" Variant="Variant.Filled" ButtonType="ButtonType.Submit" EndIcon="@Icons.Material.Filled.ArrowForward">Sign In</MudButton>
<MudText Typo="Typo.caption" Align="Align.Center">Try the seeded accounts <b>admin/admin</b> or <b>user/user</b>.</MudText>
</MudStack>
</UAuthLoginForm>
}
else
{
<MudForm @ref="form"><MudStack Spacing="2">
<MudTextField @bind-Value="username" Label="Username" Variant="Variant.Outlined" Required="true" />
<MudTextField @bind-Value="email" Label="Email" Variant="Variant.Outlined" Required="true" />
<MudPasswordField @bind-Value="password" Label="Password" Variant="Variant.Outlined" Required="true" />

Check warning on line 29 in apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Access.razor

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (8.0.x)

Found markup element with unexpected name 'MudPasswordField'. If this is intended to be a component, add a @using directive for its namespace.

Check failure on line 29 in apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Access.razor

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (8.0.x)

The attribute names could not be inferred from bind attribute 'bind-Value'. Bind attributes should be of the form 'bind' or 'bind-value' along with their corresponding optional parameters like 'bind-value:event', 'bind:format' etc.

Check warning on line 29 in apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Access.razor

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (8.0.x)

Found markup element with unexpected name 'MudPasswordField'. If this is intended to be a component, add a @using directive for its namespace.

Check warning on line 29 in apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Access.razor

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (10.0.x)

Found markup element with unexpected name 'MudPasswordField'. If this is intended to be a component, add a @using directive for its namespace.

Check failure on line 29 in apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Access.razor

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (10.0.x)

The attribute names could not be inferred from bind attribute 'bind-Value'. Bind attributes should be of the form 'bind' or 'bind-value' along with their corresponding optional parameters like 'bind-value:event', 'bind:format' etc.

Check warning on line 29 in apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Access.razor

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (10.0.x)

Found markup element with unexpected name 'MudPasswordField'. If this is intended to be a component, add a @using directive for its namespace.

Check warning on line 29 in apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Access.razor

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (9.0.x)

Found markup element with unexpected name 'MudPasswordField'. If this is intended to be a component, add a @using directive for its namespace.

Check failure on line 29 in apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Access.razor

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (9.0.x)

The attribute names could not be inferred from bind attribute 'bind-Value'. Bind attributes should be of the form 'bind' or 'bind-value' along with their corresponding optional parameters like 'bind-value:event', 'bind:format' etc.

Check warning on line 29 in apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Access.razor

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (9.0.x)

Found markup element with unexpected name 'MudPasswordField'. If this is intended to be a component, add a @using directive for its namespace.
<MudButton Class="auth-submit" Variant="Variant.Filled" OnClick="Register" EndIcon="@Icons.Material.Filled.ArrowForward">Create Account</MudButton>
</MudStack></MudForm>
}
</MudPaper>
<section class="access-features"><span>♙ <b>User Management</b><small>Real UltimateAuth users</small></span><span>⚿ <b>Live Sessions</b><small>Device-aware session chains</small></span><span>♢ <b>Authorization</b><small>Policies and permissions</small></span><span>▥ <b>Honest Data</b><small>No fabricated security events</small></span></section>
</main>

@code {
private bool create;
private string? username, email, password;

Check warning on line 39 in apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Access.razor

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (8.0.x)

Field 'Access.password' is never assigned to, and will always have its default value null

Check warning on line 39 in apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Access.razor

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (8.0.x)

Field 'Access.password' is never assigned to, and will always have its default value null

Check warning on line 39 in apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Access.razor

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (10.0.x)

Field 'Access.password' is never assigned to, and will always have its default value null

Check warning on line 39 in apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Access.razor

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (10.0.x)

Field 'Access.password' is never assigned to, and will always have its default value null

Check warning on line 39 in apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Access.razor

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (9.0.x)

Field 'Access.password' is never assigned to, and will always have its default value null

Check warning on line 39 in apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Access.razor

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (9.0.x)

Field 'Access.password' is never assigned to, and will always have its default value null
private MudForm form = null!;

private void HandleLogin(IUAuthTryResult result)
{
if (!result.Success) Snackbar.Add(result.Reason is null ? "Sign in failed." : $"Sign in failed: {result.Reason}.", Severity.Error);
}

private async Task Register()
{
await form.ValidateAsync();
if (!form.IsValid) return;
var result = await UAuthClient.Users.CreateAsync(new CodeBeam.UltimateAuth.Users.Contracts.CreateUserRequest { UserName = username, Email = email, Password = password });
if (result.IsSuccess) { Snackbar.Add("Account created. You can now sign in.", Severity.Success); create = false; }
else Snackbar.Add(result.ErrorText ?? "Account creation failed.", Severity.Error);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
@page "/dashboard"
@attribute [UAuthAuthorize]
@inherits UAuthPageBase
@inject IUAuthClient UAuthClient
@inject IUAuthClientProductInfoProvider ProductInfoProvider
@inject ISnackbar Snackbar

<PageTitle>Dashboard · Identity Lab</PageTitle>
<main class="dashboard-shell">
<aside class="side-nav">
<div><span class="lab-icon">⚗</span><strong>Identity Lab</strong><small>Playground Environment</small></div>
<nav><a class="selected">⌂ Dashboard</a><a>♙ My Identity</a><a>▱ Device Chains</a><a>♢ Authorization</a></nav>
<div class="side-bottom"><a href="https://ultimateauth.com">Documentation ↗</a><a href="https://github.com/CodeBeamOrg/UltimateAuth">GitHub ↗</a><small>A CodeBeam Product</small></div>
</aside>
<section class="dashboard-content">
<header class="dash-heading"><div><span>DASHBOARD</span><h1>Welcome back, @DisplayName.</h1><p>This is your isolated playground environment for exploring authentication,<br />authorization and session management with UltimateAuth.</p></div><MudChip T="string" Color="Color.Success" Variant="Variant.Outlined">● Playground Environment</MudChip></header>
<div class="metric-grid">
<article><i>♙</i><div><b>Current user</b><strong>@UserName</strong><small>UltimateAuth identity</small></div></article>
<article><i>▱</i><div><b>Device chains</b><strong>@(chains?.TotalCount.ToString() ?? "—")</strong><small>From GetMyChainsAsync</small></div></article>
<article><i>✓</i><div><b>Session state</b><strong>@(AuthState?.Identity?.SessionState?.ToString() ?? "—")</strong><small>Server-controlled</small></div></article>
<article><i>♢</i><div><b>Authorization</b><strong>@(AuthState?.Claims.Roles.Count ?? 0)</strong><small>Assigned roles</small></div></article>
</div>
<div class="dashboard-grid">
<MudPaper Class="dash-panel chains-panel" Elevation="0">
<div class="panel-title"><h2>Your device chains</h2><MudButton Variant="Variant.Text" OnClick="LoadChains">Refresh</MudButton></div>
@if (loading) { <MudProgressLinear Indeterminate="true" Color="Color.Primary" /> }
else if (chains?.Items.Count > 0)
{
<MudTable Items="chains.Items" Dense="true" Hover="true" Elevation="0">
<HeaderContent><MudTh>Device / client</MudTh><MudTh>Last seen</MudTh><MudTh>State</MudTh><MudTh></MudTh></HeaderContent>
<RowTemplate>
<MudTd><b>@DeviceName(context)</b><br /><small>@(context.IsCurrentDevice ? "Current device" : context.Platform)</small></MudTd>
<MudTd>@FormatDate(context.LastSeenAt)</MudTd>
<MudTd><MudChip T="string" Size="Size.Small" Color="@(context.IsRevoked ? Color.Error : Color.Success)">@context.State</MudChip></MudTd>
<MudTd><MudButton Size="Size.Small" Color="Color.Error" Variant="Variant.Text" Disabled="context.IsRevoked" OnClick="() => LogoutChain(context)">@(context.IsCurrentDevice ? "Sign out" : "Log out device")</MudButton></MudTd>
</RowTemplate>
</MudTable>
}
else { <div class="empty-state"><b>No device chains returned</b><p>UltimateAuth has not exposed an active chain for this account yet.</p></div> }
</MudPaper>
<MudPaper Class="dash-panel" Elevation="0">
<div class="panel-title"><h2>Authentication state</h2></div>
<dl class="info-list"><dt>Authenticated</dt><dd>@(AuthState?.IsAuthenticated == true ? "Yes" : "No")</dd><dt>Tenant</dt><dd>@AuthState?.Identity?.Tenant.Value</dd><dt>User key</dt><dd class="truncate">@AuthState?.Identity?.UserKey.Value</dd><dt>Roles</dt><dd>@Roles</dd><dt>Client profile</dt><dd>@product?.ClientProfile</dd></dl>
</MudPaper>
<MudPaper Class="dash-panel" Elevation="0">
<div class="panel-title"><h2>Quick actions</h2></div>
<div class="quick-actions"><MudButton Variant="Variant.Outlined" StartIcon="@Icons.Material.Filled.Refresh" OnClick="Validate">Validate session</MudButton><MudButton Variant="Variant.Outlined" StartIcon="@Icons.Material.Filled.Devices" OnClick="LoadChains">Explore chains</MudButton><MudButton Variant="Variant.Outlined" Color="Color.Error" StartIcon="@Icons.Material.Filled.Logout" OnClick="Logout">Sign out</MudButton></div>
</MudPaper>
<MudPaper Class="dash-panel" Elevation="0">
<div class="panel-title"><h2>Playground info</h2></div>
<dl class="info-list"><dt>Environment</dt><dd><span class="green-dot">●</span> Isolated</dd><dt>Storage</dt><dd>In-memory</dd><dt>Started</dt><dd>@product?.StartedAt.ToLocalTime().ToString("g")</dd><dt>UltimateAuth</dt><dd>v@product?.Version</dd></dl>
</MudPaper>
</div>
</section>
</main>

@code {
private PagedResult<SessionChainSummary>? chains;
private UAuthClientProductInfo? product;
private bool loading;
private string DisplayName => AuthState?.Identity?.DisplayName ?? AuthState?.Identity?.PrimaryUserName ?? "Explorer";
private string UserName => AuthState?.Identity?.PrimaryUserName ?? "—";
private string Roles => AuthState is null || AuthState.Claims.Roles.Count == 0 ? "None" : string.Join(", ", AuthState.Claims.Roles);

protected override async Task OnInitializedAsync()
{
product = ProductInfoProvider.Get();
if (AuthState.IsAuthenticated)
await LoadChains();
}

protected override void HandleAuthStateChanged(UAuthStateChangeReason reason)
{
if (reason == UAuthStateChangeReason.Authenticated && chains is null)
_ = InvokeAsync(async () => { await LoadChains(); StateHasChanged(); });
}
private async Task LoadChains() { loading = true; var result = await UAuthClient.Sessions.GetMyChainsAsync(); chains = result.Value; loading = false; }
private async Task LogoutChain(SessionChainSummary chain)
{
if (chain.IsCurrentDevice)
{
await UAuthClient.Flows.LogoutAsync();
return;
}

var request = new CodeBeam.UltimateAuth.Users.Contracts.LogoutDeviceRequest { ChainId = chain.ChainId };
var result = await UAuthClient.Flows.LogoutMyDeviceAsync(request);
Snackbar.Add(result.IsSuccess ? "Device logged out." : result.ErrorText ?? "Device logout failed.", result.IsSuccess ? Severity.Success : Severity.Error);
await LoadChains();
}
private async Task Validate() { var result = await UAuthClient.Flows.ValidateAsync(); Snackbar.Add(result.IsValid ? "Session is valid." : $"Session state: {result.State}", result.IsValid ? Severity.Success : Severity.Warning); }
private Task Logout() => UAuthClient.Flows.LogoutAsync();
private static string DeviceName(SessionChainSummary chain) => string.Join(" · ", new[] { chain.Browser, chain.OperatingSystem, chain.DeviceType }.Where(x => !string.IsNullOrWhiteSpace(x)));
private static string FormatDate(DateTimeOffset? value) => value?.ToLocalTime().ToString("g") ?? "Not reported";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
@page "/"
@inject NavigationManager Navigation

<PageTitle>UltimateAuth Identity Lab</PageTitle>
<main class="landing-shell @(leaving ? "leaving" : null)">
<section class="hero">
<div class="hero-copy">
<div class="eyebrow">ULTIMATEAUTH</div>
<h1>Identity <span>Lab</span></h1>
<h2>A live playground and sandbox for authentication, authorization and session management.</h2>
<p>Explore real users, device chains, sessions and permissions in an isolated environment built with Blazor, MudBlazor and UltimateAuth.</p>
<MudButton Class="explore-button" Variant="Variant.Filled" EndIcon="@Icons.Material.Filled.ArrowForward" OnClick="StartExploring">Start Exploring</MudButton>
</div>
<div class="hero-preview" aria-label="Identity Lab dashboard preview">
<div class="preview-top"><strong><span class="mini-mark">U</span> UltimateAuth</strong><span>⌕ &nbsp; Playground</span></div>
<div class="preview-body">
<aside>▣ Dashboard<br />♙ Users<br />▱ Sessions<br />◇ Authorization</aside>
<div><h3>Dashboard</h3><p>Overview of your playground environment.</p><div class="preview-cards"><b>Identity<br /><em>Real</em></b><b>Device chains<br /><em>Live</em></b><b>Auth state<br /><em>Secure</em></b></div><div class="preview-events"><strong>UltimateAuth capabilities</strong><hr />Authentication <span>flow based</span><hr />Sessions <span>device aware</span><hr />Authorization <span>policy driven</span></div></div>
</div>
</div>
</section>
<section class="feature-row">
<article><i>⚗</i><div><strong>Safe Playground</strong><p>Isolated environment for learning and testing.</p></div></article>
<article><i>♢</i><div><strong>Real Authentication</strong><p>Powered by UltimateAuth. No mock authentication.</p></div></article>
<article><i>♙</i><div><strong>Full Scenarios</strong><p>Users, permissions, devices and session control.</p></div></article>
<article><i>&lt;/&gt;</i><div><strong>Open Source</strong><p>Explore the code and use it in your own projects.</p></div></article>
</section>
<footer><span>A CodeBeam Product</span><span>Playground &nbsp;&nbsp; Documentation &nbsp;&nbsp; GitHub ↗</span></footer>
</main>

@code {
private bool leaving;
private async Task StartExploring()
{
leaving = true;
await Task.Delay(180);
Navigation.NavigateTo("/access");
}
}
Loading
Loading