Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@namespace CodeBeam.UltimateAuth.Sample
@inherits ComponentBase

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 240" width="@Size" height="@Size" style="@BuildStyle()" class="@Class">

@if (Variant == UAuthLogoVariant.Brand)
{
<path fill="@ShieldColor"
d="M32.39,14.07H167.61c11.27,0,18,6.76,18,18V133.52c0,22.54-58.59,69.87-85.64,92.41-27-22.54-85.64-69.87-85.64-92.41V32.1C14.36,20.83,21.12,14.07,32.39,14.07Z" />

<path fill="@KeyColor" fill-rule="evenodd" d="@KeyPath" />
}
else
{
<path d="M32.39,14.07H167.61c11.27,0,18,6.76,18,18V133.52c0,22.54-58.59,69.87-85.64,92.41-27-22.54-85.64-69.87-85.64-92.41V32.1C14.36,20.83,21.12,14.07,32.39,14.07Z" />

<path fill-rule="evenodd" d="@KeyPath" />
}
</svg>
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;

namespace CodeBeam.UltimateAuth.Sample;

public partial class UAuthLogo : ComponentBase
{
[Parameter] public UAuthLogoVariant Variant { get; set; } = UAuthLogoVariant.Brand;

[Parameter] public int Size { get; set; } = 32;

[Parameter] public string? ShieldColor { get; set; } = "#00072d";
[Parameter] public string? KeyColor { get; set; } = "#f6f5ae";

[Parameter] public string? Class { get; set; }
[Parameter] public string? Style { get; set; }

private string BuildStyle()
{
if (Variant == UAuthLogoVariant.Mono)
return $"color: {KeyColor}; {Style}";

return Style ?? "";
}

protected string KeyPath => @"
M120.43,39.44H79.57A11.67,11.67,0,0,0,67.9,51.11V77.37
A11.67,11.67,0,0,0,79.57,89H90.51l3.89,3.9v5.32l-3.8,3.81v81.41H99
v-5.33h13.69V169H108.1v-3.8H99C99,150.76,111.9,153,111.9,153
V99.79h-8V93.32L108.19,89h12.24
A11.67,11.67,0,0,0,132.1,77.37V51.11
A11.67,11.67,0,0,0,120.43,39.44Z

M79.57,48.19h5.84a2.92,2.92 0 0 1 2.92,2.92
v5.84a2.92,2.92 0 0 1 -2.92,2.92
h-5.84a2.91,2.91 0 0 1 -2.91,-2.92
v-5.84a2.91,2.91 0 0 1 2.91,-2.92Z

M79.57,68.62h5.84a2.92,2.92 0 0 1 2.92,2.92
v5.83a2.92,2.92 0 0 1 -2.92,2.92
h-5.84a2.91,2.91 0 0 1 -2.91,-2.92
v-5.83a2.91,2.91 0 0 1 2.91,-2.92Z

M114.59,48.19h5.84a2.92,2.92 0 0 1 2.91,2.92
v5.84a2.91,2.91 0 0 1 -2.91,2.91
h-5.84a2.92,2.92 0 0 1 -2.92,-2.91
v-5.84a2.92,2.92 0 0 1 2.92,-2.92Z

M114.59,68.62h5.84a2.92,2.92 0 0 1 2.91,2.92
v5.83a2.91,2.91 0 0 1 -2.91,2.92
h-5.84a2.92,2.92 0 0 1 -2.92,-2.92
v-5.83a2.92,2.92 0 0 1 2.92,-2.92Z
";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace CodeBeam.UltimateAuth.Sample;

public enum UAuthLogoVariant
{
Brand,
Mono
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CodeBeam.MudBlazor.Extensions" Version="9.0.1" />
<PackageReference Include="MudBlazor" Version="9.0.0" />
<PackageReference Include="CodeBeam.MudBlazor.Extensions" Version="9.0.3" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.5" />
<PackageReference Include="MudBlazor" Version="9.1.0" />
<PackageReference Include="Scalar.AspNetCore.Microsoft" Version="2.13.8" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<ResourcePreloader />
<link rel="stylesheet" href="@Assets["lib/bootstrap/dist/css/bootstrap.min.css"]" />
@* <link rel="stylesheet" href="@Assets["lib/bootstrap/dist/css/bootstrap.min.css"]" /> *@
<link rel="stylesheet" href="@Assets["app.css"]" />
<link rel="stylesheet" href="@Assets["UltimateAuth.Sample.UAuthHub.styles.css"]" />
<ImportMap />
<link rel="icon" type="image/png" href="favicon.png" />
<link rel="icon" type="image/png" href="~/UltimateAuth-Logo.png" />

<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,26 @@
<MudStack Class="uauth-stack">
@if (_state == null || !_state.IsActive)
{
<MudText>
This page cannot be accessed directly.
UAuthHub login flows can only be initiated by an authorized client application.
</MudText>
<MudPage Class="d-flex align-center justify-center" FullScreen="FullScreen.FullWithoutAppbar" Column="1" Row="1">
<MudPaper Class="pa-8" Elevation="4" Style="max-width: 520px; width: 100%;">
<MudStack Spacing="3" AlignItems="AlignItems.Center">
<UAuthLogo Size="72" />

<MudText Typo="Typo.h5"><b>Access Denied</b></MudText>

<MudText Typo="Typo.body2" Align="Align.Center">
This page cannot be accessed directly.
UAuthHub login flows can only be initiated by an authorized client application.
</MudText>

<MudDivider Class="my-2" />

<MudText Typo="Typo.caption" Color="Color.Secondary">
UltimateAuth protects this resource based on your session and permissions.
</MudText>
</MudStack>
</MudPaper>
</MudPage>
return;
}
<UAuthLoginForm Identifier="@_username" Secret="@_password" LoginType="UAuthLoginType.Pkce">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,131 +6,130 @@
using Microsoft.AspNetCore.WebUtilities;
using MudBlazor;

namespace CodeBeam.UltimateAuth.Sample.UAuthHub.Components.Pages
namespace CodeBeam.UltimateAuth.Sample.UAuthHub.Components.Pages;

public partial class Home
{
public partial class Home
{
[SupplyParameterFromQuery(Name = "hub")]
public string? HubKey { get; set; }
[SupplyParameterFromQuery(Name = "hub")]
public string? HubKey { get; set; }

private string? _username;
private string? _password;
private string? _username;
private string? _password;

private HubFlowState? _state;
private HubFlowState? _state;

protected override async Task OnParametersSetAsync()
protected override async Task OnParametersSetAsync()
{
if (string.IsNullOrWhiteSpace(HubKey))
{
if (string.IsNullOrWhiteSpace(HubKey))
{
_state = null;
return;
}

if (!HubSessionId.TryParse(HubKey, out var hubSessionId))
_state = await HubFlowReader.GetStateAsync(hubSessionId);
_state = null;
return;
}

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender)
return;

var currentError = await BrowserStorage.GetAsync(StorageScope.Session, "uauth:last_error");

if (!string.IsNullOrWhiteSpace(currentError))
{
Snackbar.Add(ResolveErrorMessage(currentError), Severity.Error);
await BrowserStorage.RemoveAsync(StorageScope.Session, "uauth:last_error");
}

var uri = Nav.ToAbsoluteUri(Nav.Uri);
var query = QueryHelpers.ParseQuery(uri.Query);

if (query.TryGetValue("__uauth_error", out var error))
{
await BrowserStorage.SetAsync(StorageScope.Session, "uauth:last_error", error.ToString());
}

if (string.IsNullOrWhiteSpace(HubKey))
{
return;
}

if (_state is null || !_state.Exists)
return;

if (_state?.IsActive != true)
{
await StartNewPkceAsync();
return;
}
}
if (HubSessionId.TryParse(HubKey, out var hubSessionId))
_state = await HubFlowReader.GetStateAsync(hubSessionId);
}

// For testing & debugging
private async Task ProgrammaticPkceLogin()
{
var hub = _state;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender)
return;

if (hub is null)
return;
var currentError = await BrowserStorage.GetAsync(StorageScope.Session, "uauth:last_error");

if (!HubSessionId.TryParse(HubKey, out var hubSessionId))
return;
if (!string.IsNullOrWhiteSpace(currentError))
{
Snackbar.Add(ResolveErrorMessage(currentError), Severity.Error);
await BrowserStorage.RemoveAsync(StorageScope.Session, "uauth:last_error");
}

var credentials = await HubCredentialResolver.ResolveAsync(hubSessionId);
var uri = Nav.ToAbsoluteUri(Nav.Uri);
var query = QueryHelpers.ParseQuery(uri.Query);

var request = new PkceLoginRequest
{
Identifier = "admin",
Secret = "admin",
AuthorizationCode = credentials?.AuthorizationCode ?? string.Empty,
CodeVerifier = credentials?.CodeVerifier ?? string.Empty,
ReturnUrl = _state?.ReturnUrl ?? string.Empty
};
await UAuthClient.Flows.CompletePkceLoginAsync(request);
if (query.TryGetValue("__uauth_error", out var error))
{
await BrowserStorage.SetAsync(StorageScope.Session, "uauth:last_error", error.ToString());
}

if (string.IsNullOrWhiteSpace(HubKey))
{
return;
}

private async Task StartNewPkceAsync()
if (_state is null || !_state.Exists)
return;

if (_state?.IsActive != true)
{
var returnUrl = await ResolveReturnUrlAsync();
await UAuthClient.Flows.BeginPkceAsync(returnUrl);
await StartNewPkceAsync();
return;
}
}

private async Task<string> ResolveReturnUrlAsync()
// For testing & debugging
private async Task ProgrammaticPkceLogin()
{
var hub = _state;

if (hub is null)
return;

if (!HubSessionId.TryParse(HubKey, out var hubSessionId))
return;

var credentials = await HubCredentialResolver.ResolveAsync(hubSessionId);

var request = new PkceLoginRequest
{
var fromContext = _state?.ReturnUrl;
if (!string.IsNullOrWhiteSpace(fromContext))
return fromContext;
Identifier = "admin",
Secret = "admin",
AuthorizationCode = credentials?.AuthorizationCode ?? string.Empty,
CodeVerifier = credentials?.CodeVerifier ?? string.Empty,
ReturnUrl = _state?.ReturnUrl ?? string.Empty
};
await UAuthClient.Flows.CompletePkceLoginAsync(request);
}

var uri = Nav.ToAbsoluteUri(Nav.Uri);
var query = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query);
private async Task StartNewPkceAsync()
{
var returnUrl = await ResolveReturnUrlAsync();
await UAuthClient.Flows.BeginPkceAsync(returnUrl);
}

if (query.TryGetValue("return_url", out var ru) && !string.IsNullOrWhiteSpace(ru))
return ru!;
private async Task<string> ResolveReturnUrlAsync()
{
var fromContext = _state?.ReturnUrl;
if (!string.IsNullOrWhiteSpace(fromContext))
return fromContext;

if (query.TryGetValue("hub", out var hubKey) && !string.IsNullOrWhiteSpace(hubKey))
{
var artifact = await AuthStore.GetAsync(new AuthArtifactKey(hubKey!));
if (artifact is HubFlowArtifact flow && !string.IsNullOrWhiteSpace(flow.ReturnUrl))
return flow.ReturnUrl!;
}
var uri = Nav.ToAbsoluteUri(Nav.Uri);
var query = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query);

// Config default (recommend adding to options)
//if (!string.IsNullOrWhiteSpace(_options.Login.DefaultReturnUrl))
// return _options.Login.DefaultReturnUrl!;
if (query.TryGetValue("return_url", out var ru) && !string.IsNullOrWhiteSpace(ru))
return ru!;

return Nav.Uri;
}

private string ResolveErrorMessage(string? errorKey)
if (query.TryGetValue("hub", out var hubKey) && !string.IsNullOrWhiteSpace(hubKey))
{
if (errorKey == "invalid")
{
return "Login failed.";
}
var artifact = await AuthStore.GetAsync(new AuthArtifactKey(hubKey!));
if (artifact is HubFlowArtifact flow && !string.IsNullOrWhiteSpace(flow.ReturnUrl))
return flow.ReturnUrl!;
}

// Config default (recommend adding to options)
//if (!string.IsNullOrWhiteSpace(_options.Login.DefaultReturnUrl))
// return _options.Login.DefaultReturnUrl!;

return "Failed attempt.";
return Nav.Uri;
}

private string ResolveErrorMessage(string? errorKey)
{
if (errorKey == "invalid")
{
return "Login failed.";
}

return "Failed attempt.";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@inject NavigationManager Nav

<MudPage Class="d-flex align-center justify-center" FullScreen="FullScreen.FullWithoutAppbar" Column="1" Row="1">
<MudPaper Class="pa-8" Elevation="4" Style="max-width: 520px; width: 100%;">
<MudStack Spacing="3" AlignItems="AlignItems.Center">
<UAuthLogo Size="72" />

<MudText Typo="Typo.h5"><b>Access Denied</b></MudText>

<MudText Typo="Typo.body2" Align="Align.Center" Color="Color.Secondary">
You don’t have permission to view this page.
If you think this is a mistake, sign in with a different account or request access.
</MudText>

<MudStack Row="true" Spacing="2" Class="mt-2">
<MudButton Href="@LoginHref" Color="Color.Primary" Variant="Variant.Filled">Sign In</MudButton>
<MudButton OnClick="@GoBack" Color="Color.Primary" Variant="Variant.Outlined">Go Back</MudButton>
</MudStack>

<MudDivider Class="my-2" />

<MudText Typo="Typo.caption" Color="Color.Secondary">
UltimateAuth protects this resource based on your session and permissions.
</MudText>
</MudStack>
</MudPaper>
</MudPage>
Loading
Loading