Skip to content

Commit

Permalink
Updated sample with public keys and signatures shown and started API …
Browse files Browse the repository at this point in the history
…sample project for validating authentication non-client side.
  • Loading branch information
KristofferStrube committed Dec 26, 2023
1 parent 3a8d0ac commit e8d139b
Show file tree
Hide file tree
Showing 16 changed files with 295 additions and 27 deletions.
6 changes: 6 additions & 0 deletions Blazor.WebAuthentication.sln
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KristofferStrube.Blazor.Web
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KristofferStrube.Blazor.COSEGenerator", "tools\KristofferStrube.Blazor.COSEGenerator\KristofferStrube.Blazor.COSEGenerator.csproj", "{07B38904-3166-4DFF-A33B-3F87B782D73A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KristofferStrube.Blazor.WebAuthentication.API", "samples\KristofferStrube.Blazor.WebAuthentication.API\KristofferStrube.Blazor.WebAuthentication.API.csproj", "{3D9BCDE2-25B6-44C0-B5E1-69BE72EB1A21}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -33,6 +35,10 @@ Global
{07B38904-3166-4DFF-A33B-3F87B782D73A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{07B38904-3166-4DFF-A33B-3F87B782D73A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{07B38904-3166-4DFF-A33B-3F87B782D73A}.Release|Any CPU.Build.0 = Release|Any CPU
{3D9BCDE2-25B6-44C0-B5E1-69BE72EB1A21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3D9BCDE2-25B6-44C0-B5E1-69BE72EB1A21}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3D9BCDE2-25B6-44C0-B5E1-69BE72EB1A21}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3D9BCDE2-25B6-44C0-B5E1-69BE72EB1A21}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>

</Project>
34 changes: 34 additions & 0 deletions samples/KristofferStrube.Blazor.WebAuthentication.API/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using KristofferStrube.Blazor.WebAuthentication.API;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddCors(o => o.AddPolicy("default",
builder =>
builder.WithOrigins("https://localhost:7203",
"https://kristofferstrube.github.io")
.AllowAnyMethod()
.AllowAnyHeader()
));

WebApplication app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
_ = app
.UseSwagger()
.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseCors("default");

app.MapWebAuthenticationAPI();

app.Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"$schema": "http:https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http:https://localhost:7664",
"sslPort": 44311
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http:https://localhost:5079",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7259;http:https://localhost:5079",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Http.HttpResults;
using System.Security.Cryptography;

namespace KristofferStrube.Blazor.WebAuthentication.API;

public static class WebAuthenticationAPI
{
public static IEndpointRouteBuilder MapWebAuthenticationAPI(this IEndpointRouteBuilder builder)
{
RouteGroupBuilder group = builder
.MapGroup("WebAuthentication");

_ = group.MapGet("Register/{userName}", Register)
.WithName("Register");

return builder;
}

public static Ok<byte[]> Register(string userName)
{
return TypedResults.Ok(RandomNumberGenerator.GetBytes(32));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.0-rc.1.23421.29" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.0-rc.1.23421.29" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.0" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@
<br />
<b>Id: </b> @id
<br />
@if (challenge is not null)
{
<b>Challenge: </b> @string.Join(", ", challenge.Select(b => $"{b:X2}"))
<br />
}
@if (publicKey is not null)
{
<b>Public Key: </b> @string.Join(", ", publicKey.Select(b => $"{b:X2}"))
<br />
}
<br />
<button class="btn btn-primary" @onclick="GetCredential">Request Credentials from Id</button>

Expand All @@ -34,6 +44,12 @@
<div class="rounded p-2 text-white @(success ? "bg-success": "bg-danger")">
@(success ? "You logged in and validated your credentials" : errorMessage ?? "You were not successful in logging on.")
</div>
@if (signature is not null)
{
<b>Signature: </b> @string.Join(", ", signature.Select(b => $"{b:X2}"))
;
<br />
}
}
}
else if (errorMessage is not null)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using KristofferStrube.Blazor.CredentialManagement;
using KristofferStrube.Blazor.WebIDL;
using KristofferStrube.Blazor.WebIDL.Exceptions;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using System.Security.Cryptography;
using System.Text;

Expand All @@ -16,6 +18,15 @@ public partial class Index : ComponentBase
private string? type;
private string? id;
private string? errorMessage;
private byte[]? challenge;
private byte[]? publicKey;
private byte[]? signature;

[Inject]
public required IJSRuntime JSRuntime { get; set; }

[Inject]
public required WebAuthenticationClient WebAuthenticationClient { get; set; }

protected override async Task OnInitializedAsync()
{
Expand All @@ -26,8 +37,9 @@ protected override async Task OnInitializedAsync()

private async Task CreateCredential()
{
byte[] challenge = RandomNumberGenerator.GetBytes(32);
byte[] userId = Encoding.ASCII.GetBytes("bob");
//challenge = await WebAuthenticationClient.Register("bob");
challenge = RandomNumberGenerator.GetBytes(32);
CredentialCreationOptions options = new()
{
PublicKey = new PublicKeyCredentialCreationOptions()
Expand Down Expand Up @@ -66,6 +78,15 @@ private async Task CreateCredential()
try
{
credential = await container.CreateAsync(options) is { } c ? new PublicKeyCredential(c) : null;

AuthenticatorResponse registrationResponse = await credential.GetResponseAsync();

Check warning on line 82 in samples/KristofferStrube.Blazor.WebAuthentication.WasmExample/Pages/Index.razor.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
if (registrationResponse is AuthenticatorAttestationResponse { } registration)
{
IJSObjectReference rawBuffer = await registration.GetPublicKeyAsync();
IJSObjectReference uint8ArrayFromBuffer = await (await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./_content/KristofferStrube.Blazor.WebIDL/KristofferStrube.Blazor.WebIDL.js")).InvokeAsync<IJSObjectReference>("constructUint8Array", rawBuffer);
Uint8Array uint8Array = await Uint8Array.CreateAsync(JSRuntime, uint8ArrayFromBuffer);
publicKey = await uint8Array.GetByteArrayAsync();
}
}
catch (DOMException exception)
{
Expand Down Expand Up @@ -101,6 +122,20 @@ private async Task GetCredential()
try
{
validatedCredential = await container.GetAsync(options) is { } c ? new PublicKeyCredential(c) : null;

if (validatedCredential is not null)
{
AuthenticatorResponse registrationResponse = await validatedCredential.GetResponseAsync();
if (registrationResponse is AuthenticatorAssertionResponse { } validation)
{
IJSObjectReference rawBuffer = await validation.GetSignatureAsync();
IJSObjectReference uint8ArrayFromBuffer = await (await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./_content/KristofferStrube.Blazor.WebIDL/KristofferStrube.Blazor.WebIDL.js")).InvokeAsync<IJSObjectReference>("constructUint8Array", rawBuffer);
Uint8Array uint8Array = await Uint8Array.CreateAsync(JSRuntime, uint8ArrayFromBuffer);
signature = await uint8Array.GetByteArrayAsync();
}
}


}
catch (DOMException exception)
{
Expand All @@ -110,4 +145,5 @@ private async Task GetCredential()

successfulGettingCredential = validatedCredential is not null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,27 @@
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

if (builder.HostEnvironment.IsDevelopment())
{
_ = builder.Services.AddKeyedScoped(typeof(WebAuthenticationClient), (_, _) => new HttpClient
{
BaseAddress = new Uri("https://localhost:7259/WebAuthentication/")
});
}
else
{
_ = builder.Services.AddKeyedScoped(typeof(WebAuthenticationClient), (_, _) => new HttpClient
{
BaseAddress = new Uri("https://kristoffer-strube.dk/API/WebAuthentication/")
});
}

builder.Services.AddCredentialsService();

// For communicating with the API.
builder.Services.AddScoped<WebAuthenticationClient>();

WebAssemblyHost app = builder.Build();

await app.Services.SetupErrorHandlingJSInterop();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Net.Http.Json;

namespace KristofferStrube.Blazor.WebAuthentication.WasmExample;

public class WebAuthenticationClient
{
private readonly HttpClient httpClient;

public WebAuthenticationClient([FromKeyedServices(typeof(WebAuthenticationClient))]HttpClient httpClient)
{
this.httpClient = httpClient;
}

public async Task<byte[]?> Register(string userName)
{
return await httpClient.GetFromJsonAsync<byte[]>($"Register/{userName}");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using KristofferStrube.Blazor.WebIDL;
using Microsoft.JSInterop;

namespace KristofferStrube.Blazor.WebAuthentication;

public class AuthenticatorAssertionResponse : AuthenticatorResponse, IJSCreatable<AuthenticatorAssertionResponse>
{
public static Task<AuthenticatorAssertionResponse> CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference)
{
return Task.FromResult<AuthenticatorAssertionResponse>(new(jSRuntime, jSReference));
}

public AuthenticatorAssertionResponse(IJSRuntime jSRuntime, IJSObjectReference jSReference) : base(jSRuntime, jSReference) { }

public async Task<IJSObjectReference> GetSignatureAsync()
{
IJSObjectReference helper = await webAuthenticationHelperTask.Value;
return await helper.InvokeAsync<IJSObjectReference>("getAttribute", JSReference, "signature");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@

namespace KristofferStrube.Blazor.WebAuthentication;

public class AuthenticatorAttestationResponse : IJSCreatable<AuthenticatorAttestationResponse>
public class AuthenticatorAttestationResponse : AuthenticatorResponse, IJSCreatable<AuthenticatorAttestationResponse>
{
public IJSObjectReference JSReference => throw new NotImplementedException();
public static Task<AuthenticatorAttestationResponse> CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference)
{
return Task.FromResult<AuthenticatorAttestationResponse>(new(jSRuntime, jSReference));
}

public IJSRuntime JSRuntime => throw new NotImplementedException();
public AuthenticatorAttestationResponse(IJSRuntime jSRuntime, IJSObjectReference jSReference) : base(jSRuntime, jSReference) { }

public static Task<AuthenticatorAttestationResponse> CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference)
public async Task<IJSObjectReference> GetPublicKeyAsync()
{
throw new NotImplementedException();
return await JSReference.InvokeAsync<IJSObjectReference>("getPublicKey");
}
}
Loading

0 comments on commit e8d139b

Please sign in to comment.