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>