diff --git a/README.md b/README.md
index 1241ea0..01ab6af 100644
--- a/README.md
+++ b/README.md
@@ -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
 
diff --git a/logto-csharp.sln b/logto-csharp.sln
index f959424..88b5a42 100644
--- a/logto-csharp.sln
+++ b/logto-csharp.sln
@@ -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
@@ -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}
diff --git a/sample-wasm/App.razor b/sample-wasm/App.razor
new file mode 100644
index 0000000..e4461cf
--- /dev/null
+++ b/sample-wasm/App.razor
@@ -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>
diff --git a/sample-wasm/Pages/Home.razor b/sample-wasm/Pages/Home.razor
new file mode 100644
index 0000000..9b2b9a8
--- /dev/null
+++ b/sample-wasm/Pages/Home.razor
@@ -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>
diff --git a/sample-wasm/Pages/Home.razor.cs b/sample-wasm/Pages/Home.razor.cs
new file mode 100644
index 0000000..dbd0638
--- /dev/null
+++ b/sample-wasm/Pages/Home.razor.cs
@@ -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;
+    }
+}
diff --git a/sample-wasm/Program.cs b/sample-wasm/Program.cs
new file mode 100644
index 0000000..8b5a9f7
--- /dev/null
+++ b/sample-wasm/Program.cs
@@ -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();
diff --git a/sample-wasm/Properties/launchSettings.json b/sample-wasm/Properties/launchSettings.json
new file mode 100644
index 0000000..522e588
--- /dev/null
+++ b/sample-wasm/Properties/launchSettings.json
@@ -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"
+      }
+    }
+  }
+}
diff --git a/sample-wasm/README.md b/sample-wasm/README.md
new file mode 100644
index 0000000..5bb34ed
--- /dev/null
+++ b/sample-wasm/README.md
@@ -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
+```
diff --git a/sample-wasm/_Imports.razor b/sample-wasm/_Imports.razor
new file mode 100644
index 0000000..ae4e992
--- /dev/null
+++ b/sample-wasm/_Imports.razor
@@ -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
diff --git a/sample-wasm/sample-wasm.csproj b/sample-wasm/sample-wasm.csproj
new file mode 100644
index 0000000..b7544a1
--- /dev/null
+++ b/sample-wasm/sample-wasm.csproj
@@ -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>
diff --git a/sample-wasm/wwwroot/css/app.css b/sample-wasm/wwwroot/css/app.css
new file mode 100644
index 0000000..5181190
--- /dev/null
+++ b/sample-wasm/wwwroot/css/app.css
@@ -0,0 +1,101 @@
+@import url('open-iconic/font/css/open-iconic-bootstrap.min.css');
+
+html, body {
+    font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
+}
+
+h1:focus {
+    outline: none;
+}
+
+a, .btn-link {
+    color: #0071c1;
+}
+
+.btn-primary {
+    color: #fff;
+    background-color: #1b6ec2;
+    border-color: #1861ac;
+}
+
+.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
+  box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
+}
+
+.content {
+    padding-top: 1.1rem;
+}
+
+.valid.modified:not([type=checkbox]) {
+    outline: 1px solid #26b050;
+}
+
+.invalid {
+    outline: 1px solid red;
+}
+
+.validation-message {
+    color: red;
+}
+
+#blazor-error-ui {
+    background: lightyellow;
+    bottom: 0;
+    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
+    display: none;
+    left: 0;
+    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
+    position: fixed;
+    width: 100%;
+    z-index: 1000;
+}
+
+    #blazor-error-ui .dismiss {
+        cursor: pointer;
+        position: absolute;
+        right: 0.75rem;
+        top: 0.5rem;
+    }
+
+.blazor-error-boundary {
+    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
+    padding: 1rem 1rem 1rem 3.7rem;
+    color: white;
+}
+
+    .blazor-error-boundary::after {
+        content: "An error has occurred."
+    }
+
+.loading-progress {
+    position: relative;
+    display: block;
+    width: 8rem;
+    height: 8rem;
+    margin: 20vh auto 1rem auto;
+}
+
+    .loading-progress circle {
+        fill: none;
+        stroke: #e0e0e0;
+        stroke-width: 0.6rem;
+        transform-origin: 50% 50%;
+        transform: rotate(-90deg);
+    }
+
+        .loading-progress circle:last-child {
+            stroke: #1b6ec2;
+            stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
+            transition: stroke-dasharray 0.05s ease-in-out;
+        }
+
+.loading-progress-text {
+    position: absolute;
+    text-align: center;
+    font-weight: bold;
+    inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
+}
+
+    .loading-progress-text:after {
+        content: var(--blazor-load-percentage-text, "Loading");
+    }
diff --git a/sample-wasm/wwwroot/favicon.png b/sample-wasm/wwwroot/favicon.png
new file mode 100644
index 0000000..8422b59
Binary files /dev/null and b/sample-wasm/wwwroot/favicon.png differ
diff --git a/sample-wasm/wwwroot/icon-192.png b/sample-wasm/wwwroot/icon-192.png
new file mode 100644
index 0000000..166f56d
Binary files /dev/null and b/sample-wasm/wwwroot/icon-192.png differ
diff --git a/sample-wasm/wwwroot/index.html b/sample-wasm/wwwroot/index.html
new file mode 100644
index 0000000..35b2a17
--- /dev/null
+++ b/sample-wasm/wwwroot/index.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Logto Blazor WASM sample</title>
+    <base href="/" />
+    <link rel="stylesheet" href="css/app.css" />
+    <link rel="icon" type="image/png" href="favicon.png" />
+    <script src="https://cdn.tailwindcss.com"></script>
+</head>
+
+<body>
+    <div id="app">
+        <svg class="loading-progress">
+            <circle r="40%" cx="50%" cy="50%" />
+            <circle r="40%" cx="50%" cy="50%" />
+        </svg>
+        <div class="loading-progress-text"></div>
+    </div>
+
+    <div id="blazor-error-ui">
+        An unhandled error has occurred.
+        <a href="" class="reload">Reload</a>
+        <a class="dismiss">🗙</a>
+    </div>
+    <script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>
+    <script src="_framework/blazor.webassembly.js"></script>
+    <script src="_content/Blorc.Core/injector.js"></script>
+</body>
+
+</html>
diff --git a/src/Logto.AspNetCore.Authentication/Logto.AspNetCore.Authentication.csproj b/src/Logto.AspNetCore.Authentication/Logto.AspNetCore.Authentication.csproj
index 6ca40b7..cdf2455 100644
--- a/src/Logto.AspNetCore.Authentication/Logto.AspNetCore.Authentication.csproj
+++ b/src/Logto.AspNetCore.Authentication/Logto.AspNetCore.Authentication.csproj
@@ -1,46 +1,49 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
-  <PropertyGroup>
-    <TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
-    <Nullable>enable</Nullable>
-    <IsPackable>true</IsPackable>
-  </PropertyGroup>
-
-  <PropertyGroup>
-    <PackageId>Logto.AspNetCore.Authentication</PackageId>
-    <Version>0.1.1</Version>
-    <Description>Logto ASP.Net Core authentication SDK.</Description>
-    <Authors>Logto</Authors>
-    <Company>Silverhand, Inc.</Company>
-    <Copyright>Silverhand, Inc.</Copyright>
-    <PackageTags>logto;auth;authentication;identity;oauth2;openid-connect;oidc</PackageTags>
-    <PackageReadmeFile>README.md</PackageReadmeFile>
-    <PackageIcon>Logto.png</PackageIcon>
-    <PackageProjectUrl>https://logto.io/</PackageProjectUrl>
-    <PackageLicenseExpression>MIT</PackageLicenseExpression>
-    <RepositoryType>git</RepositoryType>
-    <RepositoryUrl>https://github.com/logto-io/csharp</RepositoryUrl>
-  </PropertyGroup>
-
-  <ItemGroup>
-    <FrameworkReference Include="Microsoft.AspNetCore.App" />
-  </ItemGroup>
-
-  <ItemGroup>
-    <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.*"
-      Condition="'$(TargetFramework)' == 'net6.0'" />
-    <PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="6.*"
-      Condition="'$(TargetFramework)' == 'net6.0'" />
-    <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.*"
-      Condition="'$(TargetFramework)' == 'net7.0'" />
-    <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.*"
-      Condition="'$(TargetFramework)' == 'net8.0'" />
-    <PackageReference Include="System.Text.Encodings.Web" Version="4.7.2" />
-  </ItemGroup>
-
-  <ItemGroup>
-    <None Include="README.md" Pack="true" PackagePath="" />
-    <None Include="Logto.png" Pack="true" PackagePath="" />
-  </ItemGroup>
-
-</Project>
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
+        <Nullable>enable</Nullable>
+        <IsPackable>true</IsPackable>
+    </PropertyGroup>
+
+    <PropertyGroup>
+        <PackageId>Logto.AspNetCore.Authentication</PackageId>
+        <Version>0.1.1</Version>
+        <Description>Logto ASP.Net Core authentication SDK.</Description>
+        <Authors>Logto</Authors>
+        <Company>Silverhand, Inc.</Company>
+        <Copyright>Silverhand, Inc.</Copyright>
+        <PackageTags>logto;auth;authentication;identity;oauth2;openid-connect;oidc</PackageTags>
+        <PackageReadmeFile>README.md</PackageReadmeFile>
+        <PackageIcon>Logto.png</PackageIcon>
+        <PackageProjectUrl>https://logto.io/</PackageProjectUrl>
+        <PackageLicenseExpression>MIT</PackageLicenseExpression>
+        <RepositoryType>git</RepositoryType>
+        <RepositoryUrl>https://github.com/logto-io/csharp</RepositoryUrl>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <FrameworkReference Include="Microsoft.AspNetCore.App" />
+    </ItemGroup>
+
+    <ItemGroup>
+        <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect"
+            Version="6.0.*"
+            Condition="'$(TargetFramework)' == 'net6.0'" />
+        <PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="6.*"
+            Condition="'$(TargetFramework)' == 'net6.0'" />
+        <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect"
+            Version="7.0.*"
+            Condition="'$(TargetFramework)' == 'net7.0'" />
+        <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect"
+            Version="8.0.*"
+            Condition="'$(TargetFramework)' == 'net8.0'" />
+        <PackageReference Include="System.Text.Encodings.Web" Version="4.7.2" />
+    </ItemGroup>
+
+    <ItemGroup>
+        <None Include="README.md" Pack="true" PackagePath="" />
+        <None Include="Logto.png" Pack="true" PackagePath="" />
+    </ItemGroup>
+
+</Project>