Skip to content

Commit

Permalink
feat: add Blazor WASM demo (#17)
Browse files Browse the repository at this point in the history
* feat: init wasm demo

* refactor: fix auth issue

* chore: add README.md

* chore: fix typos
  • Loading branch information
gao-sun authored Feb 2, 2024
1 parent 96b103c commit 7173108
Show file tree
Hide file tree
Showing 15 changed files with 455 additions and 46 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The monorepo for Logto SDKs written in C#.
- [src/Logto.AspNetCore.Authentication.Tests](./src/Logto.AspNetCore.Authentication.Tests): Tests for the ASP.NET Core authentication middleware.
- [sample](./sample): Sample ASP.NET Core web application that shows how to use the ASP.NET Core authentication middleware.
- [sample-mvc](./sample-mvc): Sample ASP.NET Core web MVC application that shows how to use the ASP.NET Core authentication middleware.
- [sample-wasm](./sample-wasm): Sample Blazor WebAssembly application that shows how to use Blorc.OpenIdConnect to authenticate users with Logto.

## Resources

Expand Down
6 changes: 6 additions & 0 deletions logto-csharp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "logto-csharp-sample", "samp
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "sample-mvc", "sample-mvc\sample-mvc.csproj", "{2C4D9EC2-8697-4217-82E8-953835F990CA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "sample-wasm", "sample-wasm\sample-wasm.csproj", "{A4D02F83-0AF6-4886-9DDC-D0E109780CD4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -38,6 +40,10 @@ Global
{2C4D9EC2-8697-4217-82E8-953835F990CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2C4D9EC2-8697-4217-82E8-953835F990CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2C4D9EC2-8697-4217-82E8-953835F990CA}.Release|Any CPU.Build.0 = Release|Any CPU
{A4D02F83-0AF6-4886-9DDC-D0E109780CD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A4D02F83-0AF6-4886-9DDC-D0E109780CD4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A4D02F83-0AF6-4886-9DDC-D0E109780CD4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A4D02F83-0AF6-4886-9DDC-D0E109780CD4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{D05B1B5F-D560-492A-A566-95888C8C5C43} = {D44D6C8F-1A29-4796-930F-B37ADB539EA3}
Expand Down
14 changes: 14 additions & 0 deletions sample-wasm/App.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@using sample_wasm.Pages

<CascadingAuthenticationState>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<Home />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<p role="alert">Sorry, there's nothing at this address.</p>
</NotFound>
</Router>
</CascadingAuthenticationState>
34 changes: 34 additions & 0 deletions sample-wasm/Pages/Home.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
@page "/"

<section class="p-4 flex flex-col gap-2">
<h1 class="text-2xl font-bold">Logto Blazor WASM sample</h1>
<p>This is the sample application for Logto integration with Blazor WASM.</p>
<section class="space-y-2">
<AuthorizeView>
<Authorized>
<p class="text-emerald-700 text-s font-bold">
You are signed in as @(@User?.Profile?.Name ?? "(unknown name)").
</p>
<h2 class="text-xl font-bold">Profile</h2>
<ul class="list-disc list-inside">
<li>Email: @(@User?.Profile?.Email ?? "(null)")</li>
<li>Email verified: @(@User?.Profile?.EmailVerified ?? false)</li>
</ul>
<p>Access token: @(@User?.AccessToken ?? "(null)")</p>
<button class="bg-violet-700 hover:bg-violet-800 text-white px-4 py-2 rounded text-sm"
@onclick="OnLogoutButtonClickAsync">
Sign out
</button>
</Authorized>
<NotAuthorized>
<p class="text-amber-600 text-s font-bold">
You are not signed in.
</p>
<button class="bg-violet-700 hover:bg-violet-800 text-white px-4 py-2 rounded text-sm"
@onclick="OnLoginButtonClickAsync">
Sign in
</button>
</NotAuthorized>
</AuthorizeView>
</section>
</section>
56 changes: 56 additions & 0 deletions sample-wasm/Pages/Home.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
namespace sample_wasm.Pages;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Blorc.OpenIdConnect;
using Microsoft.AspNetCore.Components.Authorization;

[Authorize]
public partial class Home : ComponentBase
{
[Inject]
public required IUserManager UserManager { get; set; }
public TimeSpan? SignOutTimeSpan { get; set; }

public User<Profile>? User { get; set; }

[CascadingParameter]
protected Task<AuthenticationState>? AuthenticationStateTask { get; set; }

protected override async Task OnInitializedAsync()
{
User = await UserManager.GetUserAsync<User<Profile>>(AuthenticationStateTask!);

UserManager.UserActivity += OnUserManagerUserActivity;
UserManager.UserInactivity += OnUserManagerUserInactivity;
}

private void OnUserManagerUserInactivity(object? sender, UserInactivityEventArgs args)
{
SignOutTimeSpan = args.SignOutTimeSpan;
StateHasChanged();
}

private void OnUserManagerUserActivity(object? sender, UserActivityEventArgs args)
{
SignOutTimeSpan = null;
StateHasChanged();
}

private async Task OnLoginButtonClickAsync(MouseEventArgs obj)
{
await UserManager.SignInRedirectAsync();
}

private async Task OnLogoutButtonClickAsync(MouseEventArgs obj)
{
await UserManager.SignOutRedirectAsync();
}

public void Dispose()
{
UserManager.UserActivity -= OnUserManagerUserActivity;
UserManager.UserInactivity -= OnUserManagerUserInactivity;
}
}
30 changes: 30 additions & 0 deletions sample-wasm/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Blorc.OpenIdConnect;
using Blorc.Services;
using sample_wasm;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

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

builder.Services.AddBlorcCore();
builder.Services.AddAuthorizationCore();
builder.Services.AddBlorcOpenIdConnect(
options =>
{
builder.Configuration.Bind("IdentityServer", options);
});

var webAssemblyHost = builder.Build();

await webAssemblyHost
.ConfigureDocumentAsync(async documentService =>
{
await documentService.InjectBlorcCoreJsAsync();
await documentService.InjectOpenIdConnectAsync();
});

await webAssemblyHost.RunAsync();
31 changes: 31 additions & 0 deletions sample-wasm/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:40255",
"sslPort": 44360
}
},
"profiles": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "https://localhost:7119;http://localhost:5025",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
75 changes: 75 additions & 0 deletions sample-wasm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Logto ASP.NET Blazor WebAssembly sample project

This sample project shows how to use the [Blorc.OpenIdConnect](https://github.com/WildGums/Blorc.OpenIdConnect) to authenticate users with Logto in a Blazor WebAssembly application.

## Prerequisites

- .NET 6.0 or higher
- A [Logto Cloud](https://logto.io/) account or a self-hosted Logto
- A Logto single-page application created

### Optional

- Set up an API resource in Logto

If you don't have the Logto application created, please follow the [⚡ Get started](https://docs.logto.io/docs/tutorials/get-started/) guide to create one.

## Configuration

Create an `appsettings.Development.json` (or `appsettings.json`) with the following structure:

```jsonc
{
// ...
"IdentityServer": {
"Authority": "https://<your-logto-endpoint>/oidc",
"ClientId": "<your-logto-app-id>",
"PostLogoutRedirectUri": "<your-app-url>", // Remember to configure this in Logto
"RedirectUri": "<your-app-url>", // Remember to configure this in Logto
"ResponseType": "code",
"Scope": "openid profile" // Add more scopes if needed
}
}
```

### Fetch user info

For some special claims, such as `custom_data`, calling the `/userinfo` endpoint is required. To enable this feature, add the following configuration:

```jsonc
{
// ...
"IdentityServer": {
// ...
"LoadUserInfo": true
}
}
```

> [!Caution]
> Since WebAssembly is a client-side application, the token request will only be sent to the server-side once. Due to this nature, `LoadUserInfo` is conflict with fetching access token for API resources.
### JWT access token

If you need to fetch an access token in JWT format for an API resource, add the following configuration:

```jsonc
{
// ...
"IdentityServer": {
// ...
"Resource": "https://<your-api-resource-indicator>",
"ExtraTokenParams": {
"resource": "https://<your-api-resource-indicator>" // Ensure the key is lowercase
}
}
}
```

The value of `Resource` and `ExtraTokenParams.resource` should be the same.

## Run the sample

```bash
dotnet run # or `dotnet watch` to run in watch mode
```
10 changes: 10 additions & 0 deletions sample-wasm/_Imports.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using sample_wasm
15 changes: 15 additions & 0 deletions sample-wasm/sample-wasm.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

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

<ItemGroup>
<PackageReference Include="Blorc.OpenIdConnect" Version="1.9.0-beta0001" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.1" PrivateAssets="all" />
</ItemGroup>

</Project>
Loading

0 comments on commit 7173108

Please sign in to comment.