aboutsummaryrefslogtreecommitdiff
path: root/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2024-01-06 18:06:01 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2024-01-06 18:06:01 -0500
commit3bd7effc15d0b87adce01281b073aa1db67d3cba (patch)
treee8fcf15b9d6664bcd48bb17ac2c71c70abda204d /plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0
parentf4c2c9e148374f462592c19e8ffd4db14672805d (diff)
social portal conversion, pull provider libraries & include some prebuilts
Diffstat (limited to 'plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0')
-rw-r--r--plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/README.md15
-rw-r--r--plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/build.readme.md11
-rw-r--r--plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/Auth0Portal.cs67
-rw-r--r--plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/Endpoints/LoginEndpoint.cs191
-rw-r--r--plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/Endpoints/LogoutEndpoint.cs70
-rw-r--r--plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/VNLib.Plugins.Essentials.Auth.Auth0.csproj48
6 files changed, 402 insertions, 0 deletions
diff --git a/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/README.md b/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/README.md
new file mode 100644
index 0000000..1cacc6b
--- /dev/null
+++ b/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/README.md
@@ -0,0 +1,15 @@
+# VNLib.Plugins.Essentials.Auth.Auth0
+*A runtime asset library that provides enterprise Auth0 OAuth authentication to your application that is using the [Auth.Social](../../VNLib.Plugins.Essentials.Auth.Social) plugin*
+
+## Builds
+Debug build w/ symbols & xml docs, release builds, NuGet packages, and individually packaged source code are available on my website (link below).
+
+## Docs and Guides
+Documentation, specifications, and setup guides are available on my website.
+
+[Docs and Articles](https://www.vaughnnugent.com/resources/software/articles?tags=docs,_VNLib.Plugins.Essentials.Auth.Auth0)
+[Builds and Source](https://www.vaughnnugent.com/resources/software/modules/Plugins.Essentials)
+[Nuget Feeds](https://www.vaughnnugent.com/resources/software/modules)
+
+## License
+Source files in for this project are licensed to you under the GNU Affero General Public License (or any later version). See the LICENSE files for more information. \ No newline at end of file
diff --git a/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/build.readme.md b/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/build.readme.md
new file mode 100644
index 0000000..4533b0d
--- /dev/null
+++ b/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/build.readme.md
@@ -0,0 +1,11 @@
+VNLib.Plugins.Essentials.Auth.Auth0 Copyright © 2024 Vaughn Nugent
+Contact: Vaughn Nugent vnpublic[at]proton.me
+
+LICENSE:
+You should have recieved a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+In the future there will be more information for installtion in this file, but for now go to my website
+
+https://www.vaughnnugent.com/resources/software/articles?tags=docs,_VNLib.Plugins.Essentials.Auth.Auth0
+
+Thank you! \ No newline at end of file
diff --git a/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/Auth0Portal.cs b/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/Auth0Portal.cs
new file mode 100644
index 0000000..0ae92f4
--- /dev/null
+++ b/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/Auth0Portal.cs
@@ -0,0 +1,67 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.Auth.Auth0
+* File: Auth0Portal.cs
+*
+* Auth0Portal.cs is part of VNLib.Plugins.Essentials.Auth.Auth0 which is
+* part of the larger VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Essentials.Auth.Auth0 is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Plugins.Essentials.Auth.Auth0 is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+
+using VNLib.Plugins.Extensions.Loading;
+using VNLib.Plugins.Extensions.Loading.Routing;
+using VNLib.Plugins.Essentials.Auth.Social;
+
+using VNLib.Plugins.Essentials.Auth.Auth0.Endpoints;
+
+namespace VNLib.Plugins.Essentials.Auth.Auth0
+{
+
+ [ServiceExport]
+ [ConfigurationName(ConfigKey)]
+ public sealed class Auth0Portal : IOAuthProvider
+ {
+ internal const string ConfigKey = "auth0";
+
+ private readonly LoginEndpoint _loginEndpoint;
+ private readonly LogoutEndpoint _logoutEndpoint;
+
+ public Auth0Portal(PluginBase plugin, IConfigScope config)
+ {
+ //Init the login endpoint
+ _loginEndpoint = plugin.Route<LoginEndpoint>();
+ _logoutEndpoint = plugin.Route<LogoutEndpoint>();
+ }
+
+ ///<inheritdoc/>
+ public SocialOAuthPortal[] GetPortals()
+ {
+
+ //Return the Auth0 portal
+ return [
+ new SocialOAuthPortal(
+ ConfigKey,
+ _loginEndpoint,
+ _logoutEndpoint
+ )
+ ];
+
+ }
+ }
+}
diff --git a/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/Endpoints/LoginEndpoint.cs b/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/Endpoints/LoginEndpoint.cs
new file mode 100644
index 0000000..52be461
--- /dev/null
+++ b/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/Endpoints/LoginEndpoint.cs
@@ -0,0 +1,191 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.Auth.Auth0
+* File: LoginEndpoint.cs
+*
+* LoginEndpoint.cs is part of VNLib.Plugins.Essentials.Auth.Auth0 which is
+* part of the larger VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Essentials.Auth.Auth0 is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Plugins.Essentials.Auth.Auth0 is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Linq;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
+using RestSharp;
+
+using VNLib.Hashing;
+using VNLib.Hashing.IdentityUtility;
+using VNLib.Utils.Logging;
+using VNLib.Plugins.Essentials.Accounts;
+using VNLib.Plugins.Extensions.Loading;
+using VNLib.Net.Rest.Client.Construction;
+using VNLib.Plugins.Essentials.Auth.Social;
+
+/*
+ * Provides specialized login for Auth0 identity managment system. Auth0 apis use JWT tokens
+ * and JWK signing keys. Keys are downloaded when the plugin is first loaded and cached for
+ * the lifetime of the plugin. The keys are used to verify the JWT token and extract the user
+ */
+
+namespace VNLib.Plugins.Essentials.Auth.Auth0.Endpoints
+{
+
+ [ConfigurationName(Auth0Portal.ConfigKey)]
+ internal sealed class LoginEndpoint : SocialOauthBase
+ {
+ private readonly IAsyncLazy<ReadOnlyJsonWebKey[]> Auth0VerificationJwk;
+ private readonly bool VerifyEmail;
+
+ public LoginEndpoint(PluginBase plugin, IConfigScope config) : base(plugin, config)
+ {
+ string keyUrl = config["key_url"].GetString() ?? throw new KeyNotFoundException("Missing Auth0 'key_url' from config");
+
+ //Define the key endpoint
+ SiteAdapter.DefineSingleEndpoint()
+ .WithEndpoint<GetKeyRequest>()
+ .WithUrl(keyUrl)
+ .WithMethod(Method.Get)
+ .WithHeader("Accept", "application/json")
+ .OnResponse((r, res) => res.ThrowIfError());
+
+ //Check for email verification
+ VerifyEmail = config.TryGetValue("verified_email", out JsonElement el) && el.GetBoolean();
+
+ //Get certificate on background thread
+ Auth0VerificationJwk = Task.Run(GetRsaCertificate).AsLazy();
+ }
+
+ private async Task<ReadOnlyJsonWebKey[]> GetRsaCertificate()
+ {
+ try
+ {
+ Log.Debug("Getting Auth0 signing keys");
+
+ //rent client from pool
+ RestResponse response = await SiteAdapter.ExecuteAsync(new GetKeyRequest());
+
+ //Get response as doc
+ using JsonDocument doc = JsonDocument.Parse(response.RawBytes);
+
+ //Create a new jwk from each key element in the response
+ ReadOnlyJsonWebKey[] keys = doc.RootElement.GetProperty("keys")
+ .EnumerateArray()
+ .Select(static k => new ReadOnlyJsonWebKey(k))
+ .ToArray();
+
+ Log.Debug("Found {count} Auth0 signing keys", keys.Length);
+
+ return keys;
+ }
+ catch (Exception e)
+ {
+ Log.Error(e, "Failed to get Auth0 signing keys");
+ throw;
+ }
+ }
+
+ /*
+ * Auth0 uses the format "platoform|{user_id}" for the user id so it should match the
+ * external platofrm as github and discord endoints also
+ */
+
+ private static string GetUserIdFromPlatform(string userName)
+ {
+ return ManagedHash.ComputeHash(userName, HashAlg.SHA1, HashEncodingMode.Hexadecimal);
+ }
+
+
+ private static readonly Task<UserLoginData?> EmptyLoginData = Task.FromResult<UserLoginData?>(null);
+ private static readonly Task<AccountData?> EmptyUserData = Task.FromResult<AccountData?>(null);
+
+ ///<inheritdoc/>
+ protected override Task<UserLoginData?> GetLoginDataAsync(IOAuthAccessState clientAccess, CancellationToken cancellation)
+ {
+ //recover the identity token
+ using JsonWebToken jwt = JsonWebToken.Parse(clientAccess.IdToken);
+
+ //Verify the token against the first signing key
+ if (!jwt.VerifyFromJwk(Auth0VerificationJwk.Value[0]))
+ {
+ return EmptyLoginData;
+ }
+
+ using JsonDocument userData = jwt.GetPayload();
+
+ int iat = userData.RootElement.GetProperty("iat").GetInt32();
+ int exp = userData.RootElement.GetProperty("exp").GetInt32();
+
+ string userId = userData.RootElement.GetProperty("sub").GetString() ?? throw new Exception("Missing sub in jwt");
+ string audience = userData.RootElement.GetProperty("aud").GetString() ?? throw new Exception("Missing aud in jwt");
+ string issuer = userData.RootElement.GetProperty("iss").GetString() ?? throw new Exception("Missing iss in jwt");
+
+ if (exp < DateTimeOffset.UtcNow.ToUnixTimeSeconds())
+ {
+ //Expired
+ return EmptyLoginData;
+ }
+
+ //Verify audience matches client id
+ if (!Config.ClientID.Value.Equals(audience, StringComparison.Ordinal))
+ {
+ //Invalid audience
+ return EmptyLoginData;
+ }
+
+ return Task.FromResult<UserLoginData?>(new UserLoginData()
+ {
+ UserId = GetUserIdFromPlatform(userId)
+ });
+ }
+
+ /*
+ * Account data may be recovered from the identity token
+ * and it happens after a call to GetLoginData so
+ * we do not need to re-verify the token
+ */
+ ///<inheritdoc/>
+ protected override Task<AccountData?> GetAccountDataAsync(IOAuthAccessState clientAccess, CancellationToken cancellationToken)
+ {
+ //Parse token again to get the user data
+ using JsonWebToken jwt = JsonWebToken.Parse(clientAccess.IdToken);
+
+ using JsonDocument userData = jwt.GetPayload();
+
+ //Confirm email is verified
+ if (!userData.RootElement.GetProperty("email_verified").GetBoolean() && VerifyEmail)
+ {
+ return EmptyUserData;
+ }
+
+ string fullName = userData.RootElement.GetProperty("name").GetString() ?? " ";
+
+ return Task.FromResult<AccountData?>(new AccountData()
+ {
+ EmailAddress = userData.RootElement.GetProperty("email").GetString(),
+ First = fullName.Split(' ').FirstOrDefault(),
+ Last = fullName.Split(' ').LastOrDefault(),
+ });
+ }
+
+ private sealed record class GetKeyRequest()
+ { }
+ }
+}
diff --git a/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/Endpoints/LogoutEndpoint.cs b/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/Endpoints/LogoutEndpoint.cs
new file mode 100644
index 0000000..497357a
--- /dev/null
+++ b/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/Endpoints/LogoutEndpoint.cs
@@ -0,0 +1,70 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.Auth.Auth0
+* File: LogoutEndpoint.cs
+*
+* LogoutEndpoint.cs is part of VNLib.Plugins.Essentials.Auth.Auth0 which is
+* part of the larger VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Essentials.Auth.Auth0 is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Plugins.Essentials.Auth.Auth0 is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using VNLib.Utils;
+
+using VNLib.Plugins.Extensions.Loading;
+using VNLib.Plugins.Essentials.Accounts;
+using VNLib.Plugins.Essentials.Endpoints;
+using VNLib.Plugins.Essentials.Extensions;
+
+
+namespace VNLib.Plugins.Essentials.Auth.Auth0.Endpoints
+{
+ [ConfigurationName(Auth0Portal.ConfigKey)]
+ internal sealed class LogoutEndpoint : ProtectedWebEndpoint
+ {
+ private readonly IAsyncLazy<string> ReturnUrl;
+
+ public LogoutEndpoint(PluginBase plugin, IConfigScope config)
+ {
+ string returnToUrl = config.GetRequiredProperty("return_to_url", p => p.GetString()!);
+ string logoutUrl = config.GetRequiredProperty("logout_url", p => p.GetString()!);
+ string path = config.GetRequiredProperty("path", p => p.GetString()!);
+
+ InitPathAndLog($"{path}/logout", plugin.Log);
+
+ //Build the return url once the client id is available
+ ReturnUrl = plugin.GetSecretAsync("auth0_client_id").ToLazy(sr =>
+ {
+ return $"{logoutUrl}?client_id={sr.Result.ToString()}&returnTo={returnToUrl}";
+ });
+ }
+
+ protected override ERRNO PreProccess(HttpEntity entity)
+ {
+ //Client required to be fully authorized
+ return base.PreProccess(entity)
+ && entity.IsClientAuthorized(AuthorzationCheckLevel.Critical);
+ }
+
+ protected override VfReturnType Post(HttpEntity entity)
+ {
+ //Invalidate the login before redirecting the client
+ entity.InvalidateLogin();
+ entity.Redirect(RedirectType.Temporary, ReturnUrl.Value);
+ return VfReturnType.VirtualSkip;
+ }
+ }
+} \ No newline at end of file
diff --git a/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/VNLib.Plugins.Essentials.Auth.Auth0.csproj b/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/VNLib.Plugins.Essentials.Auth.Auth0.csproj
new file mode 100644
index 0000000..2beb64f
--- /dev/null
+++ b/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/VNLib.Plugins.Essentials.Auth.Auth0.csproj
@@ -0,0 +1,48 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <Nullable>enable</Nullable>
+ <TargetFramework>net8.0</TargetFramework>
+ <RootNamespace>VNLib.Plugins.Essentials.Auth.Auth0</RootNamespace>
+ <AssemblyName>VNLib.Plugins.Essentials.Auth.Auth0</AssemblyName>
+ <GenerateDocumentationFile>True</GenerateDocumentationFile>
+ <AnalysisLevel>latest-all</AnalysisLevel>
+ <NeutralLanguage>en-US</NeutralLanguage>
+ <EnableDynamicLoading>true</EnableDynamicLoading>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <Authors>Vaughn Nugent</Authors>
+ <Company>Vaughn Nugent</Company>
+ <Product>VNLib.Plugins.Essentials.Auth.Auth0</Product>
+ <PackageId>VNLib.Plugins.Essentials.Auth.Auth0</PackageId>
+ <Description>A runtime asset library that adds Auth0 social OAuth autentication integration with Auth.Social plugin library</Description>
+ <Copyright>Copyright © 2024 Vaughn Nugent</Copyright>
+ <PackageProjectUrl>https://www.vaughnnugent.com/resources/software/modules/Plugins.Essentials</PackageProjectUrl>
+ <RepositoryUrl>https://Auth0.com/VnUgE/Plugins.Essentials/tree/master/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0</RepositoryUrl>
+ <PackageReadmeFile>README.md</PackageReadmeFile>
+ <PackageLicenseFile>LICENSE</PackageLicenseFile>
+ <PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <None Include="..\..\..\..\LICENSE">
+ <Pack>True</Pack>
+ <PackagePath>\</PackagePath>
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </None>
+ <None Include="..\README.md">
+ <Pack>True</Pack>
+ <PackagePath>\</PackagePath>
+ </None>
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\..\plugins\VNLib.Plugins.Essentials.Auth.Social\src\VNLib.Plugins.Essentials.Auth.Social.csproj" />
+ </ItemGroup>
+
+ <Target Condition="'$(BuildingInsideVisualStudio)' == true" Name="PostBuild" AfterTargets="PostBuildEvent">
+ <Exec Command="start xcopy &quot;$(TargetDir)&quot; &quot;$(SolutionDir)devplugins\RuntimeAssets\$(TargetName)&quot; /E /Y /R" />
+ </Target>
+
+</Project>