aboutsummaryrefslogtreecommitdiff
path: root/libs/VNLib.Plugins.Sessions.OAuth/src
diff options
context:
space:
mode:
Diffstat (limited to 'libs/VNLib.Plugins.Sessions.OAuth/src')
-rw-r--r--libs/VNLib.Plugins.Sessions.OAuth/src/Endpoints/AccessTokenEndpoint.cs20
-rw-r--r--libs/VNLib.Plugins.Sessions.OAuth/src/Endpoints/RevocationEndpoint.cs8
-rw-r--r--libs/VNLib.Plugins.Sessions.OAuth/src/GetTokenResult.cs36
-rw-r--r--libs/VNLib.Plugins.Sessions.OAuth/src/IApplicationTokenFactory.cs (renamed from libs/VNLib.Plugins.Sessions.OAuth/src/TokenAndSessionIdResult.cs)27
-rw-r--r--libs/VNLib.Plugins.Sessions.OAuth/src/IOauthSessionIdFactory.cs27
-rw-r--r--libs/VNLib.Plugins.Sessions.OAuth/src/O2SessionProviderEntry.cs65
-rw-r--r--libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2Session.cs83
-rw-r--r--libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionConfig.cs70
-rw-r--r--libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionFactory.cs41
-rw-r--r--libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionIdProvider.cs131
-rw-r--r--libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionProvider.cs212
-rw-r--r--libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionStore.cs101
-rw-r--r--libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2TokenFactory.cs99
13 files changed, 507 insertions, 413 deletions
diff --git a/libs/VNLib.Plugins.Sessions.OAuth/src/Endpoints/AccessTokenEndpoint.cs b/libs/VNLib.Plugins.Sessions.OAuth/src/Endpoints/AccessTokenEndpoint.cs
index f01b764..9f0f35d 100644
--- a/libs/VNLib.Plugins.Sessions.OAuth/src/Endpoints/AccessTokenEndpoint.cs
+++ b/libs/VNLib.Plugins.Sessions.OAuth/src/Endpoints/AccessTokenEndpoint.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Essentials.Sessions.OAuth
@@ -25,7 +25,6 @@
using System;
using System.Net;
using System.Text.Json;
-using System.Threading;
using System.Threading.Tasks;
using VNLib.Utils.Memory;
@@ -42,17 +41,16 @@ using VNLib.Plugins.Extensions.Validation;
namespace VNLib.Plugins.Sessions.OAuth.Endpoints
{
- delegate Task<IOAuth2TokenResult?> CreateTokenImpl(HttpEntity ev, UserApplication application, CancellationToken cancellation = default);
/// <summary>
/// Grants authorization to OAuth2 clients to protected resources
/// with access tokens
/// </summary>
+ [ConfigurationName(O2SessionProviderEntry.OAUTH2_CONFIG_KEY)]
internal sealed class AccessTokenEndpoint : ResourceEndpointBase
{
- private readonly CreateTokenImpl CreateToken;
+ private readonly IApplicationTokenFactory TokenFactory;
private readonly ApplicationStore Applications;
-
private readonly Task<ReadOnlyJsonWebKey?> JWTVerificationKey;
//override protection settings to allow most connections to authenticate
@@ -63,11 +61,17 @@ namespace VNLib.Plugins.Sessions.OAuth.Endpoints
DisableVerifySessionCors = true
};
- public AccessTokenEndpoint(string path, PluginBase pbase, CreateTokenImpl tokenStore)
+ public AccessTokenEndpoint(PluginBase pbase, IConfigScope config)
{
+ string? path = config["token_path"].GetString();;
+
InitPathAndLog(path, pbase.Log);
- CreateToken = tokenStore;
+
+ //Get the session provider, as its a token factory
+ TokenFactory = pbase.GetOrCreateSingleton<OAuth2SessionProvider>();
+
Applications = new(pbase.GetContextOptions(), pbase.GetPasswords());
+
//Try to get the application token key for verifying signed application JWTs
JWTVerificationKey = pbase.TryGetSecretAsync("application_token_key").ToJsonWebKey();
}
@@ -172,7 +176,7 @@ namespace VNLib.Plugins.Sessions.OAuth.Endpoints
return VfReturnType.VirtualSkip;
}
- IOAuth2TokenResult? result = await CreateToken(entity, app, entity.EventCancellation);
+ IOAuth2TokenResult? result = await TokenFactory.CreateAccessTokenAsync(entity, app, entity.EventCancellation);
if (result == null)
{
diff --git a/libs/VNLib.Plugins.Sessions.OAuth/src/Endpoints/RevocationEndpoint.cs b/libs/VNLib.Plugins.Sessions.OAuth/src/Endpoints/RevocationEndpoint.cs
index 81f82c2..45a8391 100644
--- a/libs/VNLib.Plugins.Sessions.OAuth/src/Endpoints/RevocationEndpoint.cs
+++ b/libs/VNLib.Plugins.Sessions.OAuth/src/Endpoints/RevocationEndpoint.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Essentials.Sessions.OAuth
@@ -22,10 +22,6 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
-using System;
-using System.Text.Json;
-using System.Collections.Generic;
-
using VNLib.Plugins.Essentials;
using VNLib.Plugins.Essentials.Oauth;
using VNLib.Plugins.Extensions.Loading;
@@ -40,7 +36,7 @@ namespace VNLib.Plugins.Sessions.OAuth.Endpoints
internal class RevocationEndpoint : O2EndpointBase
{
- public RevocationEndpoint(PluginBase pbase, IReadOnlyDictionary<string, JsonElement> config)
+ public RevocationEndpoint(PluginBase pbase, IConfigScope config)
{
string? path = config["path"].GetString();
InitPathAndLog(path, pbase.Log);
diff --git a/libs/VNLib.Plugins.Sessions.OAuth/src/GetTokenResult.cs b/libs/VNLib.Plugins.Sessions.OAuth/src/GetTokenResult.cs
new file mode 100644
index 0000000..0587931
--- /dev/null
+++ b/libs/VNLib.Plugins.Sessions.OAuth/src/GetTokenResult.cs
@@ -0,0 +1,36 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.Sessions.OAuth
+* File: GetTokenResult.cs
+*
+* GetTokenResult.cs is part of VNLib.Plugins.Essentials.Sessions.OAuth which is
+* part of the larger VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Essentials.Sessions.OAuth 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.Sessions.OAuth 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/.
+*/
+
+
+namespace VNLib.Plugins.Sessions.OAuth
+{
+
+ /// <summary>
+ /// The result of generating a new access token
+ /// </summary>
+ /// <param name="AccessToken">The new access token</param>
+ /// <param name="RefreshToken">The optional refresh token</param>
+ public readonly record struct GetTokenResult(string AccessToken, string? RefreshToken)
+ { }
+}
diff --git a/libs/VNLib.Plugins.Sessions.OAuth/src/TokenAndSessionIdResult.cs b/libs/VNLib.Plugins.Sessions.OAuth/src/IApplicationTokenFactory.cs
index 6ae2af4..7f65ec3 100644
--- a/libs/VNLib.Plugins.Sessions.OAuth/src/TokenAndSessionIdResult.cs
+++ b/libs/VNLib.Plugins.Sessions.OAuth/src/IApplicationTokenFactory.cs
@@ -1,11 +1,11 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Essentials.Sessions.OAuth
-* File: TokenAndSessionIdResult.cs
+* File: IApplicationTokenFactory.cs
*
-* TokenAndSessionIdResult.cs is part of VNLib.Plugins.Essentials.Sessions.OAuth which is part of the larger
+* IApplicationTokenFactory.cs is part of VNLib.Plugins.Essentials.Sessions.OAuth which is part of the larger
* VNLib collection of libraries and utilities.
*
* VNLib.Plugins.Essentials.Sessions.OAuth is free software: you can redistribute it and/or modify
@@ -22,20 +22,17 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
+using System.Threading;
+using System.Threading.Tasks;
+
+using VNLib.Net.Http;
+using VNLib.Plugins.Essentials.Oauth.Tokens;
+using VNLib.Plugins.Essentials.Oauth.Applications;
namespace VNLib.Plugins.Sessions.OAuth
{
- public readonly struct TokenAndSessionIdResult
+ interface IApplicationTokenFactory
{
- public readonly string SessionId;
- public readonly string AccessToken;
- public readonly string? RefreshToken;
-
- public TokenAndSessionIdResult(string sessionId, string token, string? refreshToken)
- {
- SessionId = sessionId;
- AccessToken = token;
- RefreshToken = refreshToken;
- }
+ Task<IOAuth2TokenResult?> CreateAccessTokenAsync(IHttpEvent ev, UserApplication app, CancellationToken cancellation);
}
-}
+} \ No newline at end of file
diff --git a/libs/VNLib.Plugins.Sessions.OAuth/src/IOauthSessionIdFactory.cs b/libs/VNLib.Plugins.Sessions.OAuth/src/IOauthSessionIdFactory.cs
index 12a7ffe..3fe508f 100644
--- a/libs/VNLib.Plugins.Sessions.OAuth/src/IOauthSessionIdFactory.cs
+++ b/libs/VNLib.Plugins.Sessions.OAuth/src/IOauthSessionIdFactory.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Essentials.Sessions.OAuth
@@ -23,11 +23,6 @@
*/
using System;
-using System.Diagnostics.CodeAnalysis;
-
-using VNLib.Net.Http;
-using VNLib.Plugins.Essentials.Oauth.Applications;
-using VNLib.Plugins.Sessions.Cache.Client;
namespace VNLib.Plugins.Sessions.OAuth
{
@@ -37,15 +32,7 @@ namespace VNLib.Plugins.Sessions.OAuth
/// The maxium number of tokens allowed to be created per OAuth application
/// </summary>
int MaxTokensPerApp { get; }
-
- /// <summary>
- /// Allows for custom configuration of the newly created session and
- /// the <see cref="IHttpEvent"/> its attached to
- /// </summary>
- /// <param name="session">The newly created session</param>
- /// <param name="app">The application associated with the session</param>
- /// <param name="entity">The http event that generated the new session</param>
- void InitNewSession(RemoteSession session, UserApplication app, IHttpEvent entity);
+
/// <summary>
/// The time a session is valid for
/// </summary>
@@ -56,19 +43,11 @@ namespace VNLib.Plugins.Sessions.OAuth
/// and required credential information to generate the new session
/// </summary>
/// <returns>The information genreated for the news ession</returns>
- TokenAndSessionIdResult GenerateTokensAndId();
+ GetTokenResult GenerateTokensAndId();
/// <summary>
/// The type of token this session provider generates
/// </summary>
string TokenType { get; }
-
- /// <summary>
- /// Attempts to recover a session id from
- /// </summary>
- /// <param name="entity">The entity to get the session-id for</param>
- /// <param name="sessionId">The found ID for the session if accepted</param>
- /// <returns>True if a session id was found or set for the session</returns>
- bool TryGetSessionId(IHttpEvent entity, [NotNullWhen(true)] out string? sessionId);
}
}
diff --git a/libs/VNLib.Plugins.Sessions.OAuth/src/O2SessionProviderEntry.cs b/libs/VNLib.Plugins.Sessions.OAuth/src/O2SessionProviderEntry.cs
index 92ea020..0437541 100644
--- a/libs/VNLib.Plugins.Sessions.OAuth/src/O2SessionProviderEntry.cs
+++ b/libs/VNLib.Plugins.Sessions.OAuth/src/O2SessionProviderEntry.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Essentials.Sessions.OAuth
@@ -23,34 +23,22 @@
*/
using System;
-using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
-using System.Collections.Generic;
using VNLib.Net.Http;
using VNLib.Utils.Logging;
-using VNLib.Utils.Extensions;
-using VNLib.Data.Caching;
-using VNLib.Plugins.Sessions.Cache.Client;
-using VNLib.Plugins.Essentials;
-using VNLib.Plugins.Essentials.Sessions;
-using VNLib.Plugins.Essentials.Oauth.Tokens;
-using VNLib.Plugins.Essentials.Oauth.Applications;
using VNLib.Plugins.Sessions.OAuth.Endpoints;
-using VNLib.Plugins.Extensions.VNCache;
+using VNLib.Plugins.Essentials.Sessions;
using VNLib.Plugins.Extensions.Loading;
using VNLib.Plugins.Extensions.Loading.Routing;
-using VNLib.Plugins.Extensions.Loading.Sql;
-using VNLib.Plugins.Extensions.Loading.Events;
-
namespace VNLib.Plugins.Sessions.OAuth
{
public sealed class O2SessionProviderEntry : ISessionProvider
{
- const string OAUTH2_CONFIG_KEY = "oauth2";
+ public const string OAUTH2_CONFIG_KEY = "oauth2";
private OAuth2SessionProvider? _sessions;
@@ -63,21 +51,17 @@ namespace VNLib.Plugins.Sessions.OAuth
ValueTask<SessionHandle> ISessionProvider.GetSessionAsync(IHttpEvent entity, CancellationToken cancellationToken)
{
return _sessions!.GetSessionAsync(entity, cancellationToken);
- }
-
+ }
public void Load(PluginBase plugin, ILogProvider localized)
{
- IReadOnlyDictionary<string, JsonElement> oauth2Config = plugin.GetConfigForType<OAuth2SessionProvider>();
+ IConfigScope o2Config = plugin.GetConfig(OAUTH2_CONFIG_KEY);
//Access token endpoint is optional
- if (oauth2Config.TryGetValue("token_path", out JsonElement el))
+ if (o2Config.ContainsKey("token_path"))
{
- //Init auth endpoint
- AccessTokenEndpoint authEp = new(el.GetString()!, plugin, CreateTokenDelegateAsync);
-
- //route auth endpoint
- plugin.Route(authEp);
+ //Create token endpoint
+ plugin.Route<AccessTokenEndpoint>();
}
//Optional revocation endpoint
@@ -87,38 +71,11 @@ namespace VNLib.Plugins.Sessions.OAuth
plugin.Route<RevocationEndpoint>();
}
- int cacheLimit = oauth2Config["cache_size"].GetInt32();
- int maxTokensPerApp = oauth2Config["max_tokens_per_app"].GetInt32();
- int sessionIdSize = (int)oauth2Config["access_token_size"].GetUInt32();
- TimeSpan tokenValidFor = oauth2Config["token_valid_for_sec"].GetTimeSpan(TimeParseType.Seconds);
- TimeSpan cleanupInterval = oauth2Config["gc_interval_sec"].GetTimeSpan(TimeParseType.Seconds);
- string sessionIdPrefix = oauth2Config["cache_prefix"].GetString() ?? throw new KeyNotFoundException($"Missing required key 'cache_prefix' in '{OAUTH2_CONFIG_KEY}' config");
-
- //init the id provider
- OAuth2SessionIdProvider idProv = new(sessionIdPrefix, maxTokensPerApp, sessionIdSize, tokenValidFor);
-
- //Get shared global-cache
- IGlobalCacheProvider globalCache = plugin.GetGlobalCache(localized);
-
- //Create cache store from global cache
- GlobalCacheStore cacheStore = new(globalCache);
-
- //Init session provider now that client is loaded
- _sessions = new(cacheStore, cacheLimit, 100, idProv, plugin.GetContextOptions());
-
- //Schedule cleanup interval with the plugin scheduler
- plugin.ScheduleInterval(_sessions, cleanupInterval);
-
- //Wait and cleanup expired sessions
- _ = plugin.ObserveTask(() => _sessions.CleanupExpiredSessionsAsync(localized, plugin.UnloadToken), 1000);
+ //Init session provider
+ _sessions = plugin.GetOrCreateSingleton<OAuth2SessionProvider>();
+ _sessions.SetLog(localized);
localized.Information("Session provider loaded");
-
- }
-
- private async Task<IOAuth2TokenResult?> CreateTokenDelegateAsync(HttpEntity entity, UserApplication app, CancellationToken cancellation)
- {
- return await _sessions!.CreateAccessTokenAsync(entity, app, cancellation).ConfigureAwait(false);
}
}
} \ No newline at end of file
diff --git a/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2Session.cs b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2Session.cs
index 916f55c..605ccbd 100644
--- a/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2Session.cs
+++ b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2Session.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Essentials.Sessions.OAuth
@@ -23,42 +23,34 @@
*/
using System;
-using System.Threading;
-using System.Threading.Tasks;
+using System.Collections.Generic;
using VNLib.Net.Http;
using VNLib.Plugins.Essentials.Sessions;
+using VNLib.Plugins.Essentials.Extensions;
using VNLib.Plugins.Sessions.Cache.Client;
-using VNLib.Plugins.Sessions.Cache.Client.Exceptions;
-
using static VNLib.Plugins.Essentials.Sessions.ISessionExtensions;
namespace VNLib.Plugins.Sessions.OAuth
{
+
/// <summary>
/// The implementation of the OAuth2 session container for HTTP sessions
/// </summary>
internal sealed class OAuth2Session : RemoteSession
{
- private readonly Action<OAuth2Session> InvalidateCache;
+ public OAuth2Session(string sessionId, IDictionary<string, string> data, bool isNew)
+ : base(sessionId, data, isNew)
+ {}
- /// <summary>
- /// Initalizes a new <see cref="OAuth2Session"/>
- /// </summary>
- /// <param name="sessionId">The session id (or token)</param>
- /// <param name="client">The <see cref="IRemoteCacheStore"/> used as the backing cache provider</param>
- /// <param name="backgroundTimeOut">The ammount of time to wait for a background operation (delete, update, get)</param>
- /// <param name="invalidCache">Called when the session has been marked as invalid and the close even hook is being executed</param>
- public OAuth2Session(string sessionId, IRemoteCacheStore client, TimeSpan backgroundTimeOut, Action<OAuth2Session> invalidCache)
- : base(sessionId, client, backgroundTimeOut)
+ public void InitNewSession(IHttpEvent entity)
{
- InvalidateCache = invalidCache;
- IsInvalid = false;
+ SessionType = SessionType.Web;
+ Created = DateTimeOffset.UtcNow;
+ //Set user-ip address
+ UserIP = entity.Server.GetTrustedIp();
}
- public bool IsInvalid { get; private set; }
-
-
///<inheritdoc/>
///<exception cref="NotSupportedException"></exception>
public override string Token
@@ -79,56 +71,5 @@ namespace VNLib.Plugins.Sessions.OAuth
}
base.IndexerSet(key, value);
}
- ///<inheritdoc/>
- ///<exception cref="SessionStatusException"></exception>
- public override async Task WaitAndLoadAsync(IHttpEvent entity, CancellationToken token = default)
- {
- //Wait to enter lock
- await base.WaitAndLoadAsync(entity, token);
- if (IsInvalid)
- {
- //Release lock
- MainLock.Release();
- throw new SessionStatusException("The session has been invalidated");
- }
- //Set session type
- if (IsNew)
- {
- SessionType = SessionType.OAuth2;
- }
- }
- ///<inheritdoc/>
- protected override async ValueTask<Task?> UpdateResource(bool isAsync, IHttpEvent state)
- {
- Task? result = null;
- //Invalid flag is set, so exit
- if (IsInvalid)
- {
- result = Task.CompletedTask;
- }
- //Check flags in priority level, Invalid is highest state priority
- else if (Flags.IsSet(INVALID_MSK))
- {
- //Clear all stored values
- DataStore!.Clear();
- //Delete the entity syncronously
- await ProcessDeleteAsync();
- //Set invalid flag
- IsInvalid = true;
- //Invlidate cache
- InvalidateCache(this);
- result = Task.CompletedTask;
- }
- else if (Flags.IsSet(MODIFIED_MSK))
- {
- //Send update to server
- result = Task.Run(ProcessUpdateAsync);
- }
-
- //Clear all flags
- Flags.ClearAll();
-
- return result;
- }
}
}
diff --git a/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionConfig.cs b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionConfig.cs
new file mode 100644
index 0000000..5eb9c0c
--- /dev/null
+++ b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionConfig.cs
@@ -0,0 +1,70 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.Sessions.OAuth
+* File: O2SessionProviderEntry.cs
+*
+* O2SessionProviderEntry.cs is part of VNLib.Plugins.Essentials.Sessions.OAuth which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Essentials.Sessions.OAuth 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.Sessions.OAuth 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.Text.Json.Serialization;
+
+using VNLib.Plugins.Extensions.Loading;
+
+
+namespace VNLib.Plugins.Sessions.OAuth
+{
+ sealed class OAuth2SessionConfig : IOnConfigValidation
+ {
+ [JsonPropertyName("max_tokens_per_app")]
+ public int MaxTokensPerApp { get; set; } = 10;
+
+ [JsonPropertyName("access_token_size")]
+ public int AccessTokenSize { get; set; } = 64;
+
+ [JsonPropertyName("token_valid_for_sec")]
+ public int TokenLifeTimeSeconds { get; set; } = 3600;
+
+ [JsonPropertyName("cache_prefix")]
+ public string CachePrefix { get; set; } = "oauth2";
+
+ public void Validate()
+ {
+ if (MaxTokensPerApp < 1)
+ {
+ throw new ArgumentOutOfRangeException("max_tokens_per_app", "You must configure at least 1 Oatuh2 access token per application, or disable this plugin");
+ }
+
+ if (AccessTokenSize < 16)
+ {
+ throw new ArgumentOutOfRangeException("access_token_size", "You must configure an access token size of at least 16 bytes in length");
+ }
+
+ if (TokenLifeTimeSeconds < 1)
+ {
+ throw new ArgumentOutOfRangeException("token_valid_for_sec", "You must configure an access token lifetime");
+ }
+
+ if (string.IsNullOrWhiteSpace(CachePrefix))
+ {
+ throw new ArgumentException("You must specify a cache prefix", "cache_prefix");
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionFactory.cs b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionFactory.cs
new file mode 100644
index 0000000..d811e25
--- /dev/null
+++ b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionFactory.cs
@@ -0,0 +1,41 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.Sessions.OAuth
+* File: OAuth2SessionFactory.cs
+*
+* OAuth2SessionFactory.cs is part of VNLib.Plugins.Essentials.Sessions.OAuth which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Essentials.Sessions.OAuth 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.Sessions.OAuth 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.Collections.Generic;
+
+using VNLib.Net.Http;
+using VNLib.Plugins.Sessions.Cache.Client;
+
+namespace VNLib.Plugins.Sessions.OAuth
+{
+ internal sealed class OAuth2SessionFactory : ISessionFactory<OAuth2Session>
+ {
+ ///<inheritdoc/>
+ public OAuth2Session? GetNewSession(IHttpEvent entity, string sessionId, IDictionary<string, string>? sessionData)
+ {
+ //Initial data should not be null, if so, do not attach session
+ return sessionData == null ? null : new(sessionId, sessionData, false);
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionIdProvider.cs b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionIdProvider.cs
deleted file mode 100644
index 5e8ad6b..0000000
--- a/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionIdProvider.cs
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.Sessions.OAuth
-* File: OAuth2SessionIdProvider.cs
-*
-* OAuth2SessionIdProvider.cs is part of VNLib.Plugins.Essentials.Sessions.OAuth which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.Sessions.OAuth 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.Sessions.OAuth 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.Diagnostics.CodeAnalysis;
-
-using VNLib.Hashing;
-using VNLib.Net.Http;
-using VNLib.Utils.Memory;
-using VNLib.Utils.Extensions;
-using VNLib.Plugins.Essentials.Extensions;
-using VNLib.Plugins.Essentials.Oauth.Applications;
-using VNLib.Plugins.Sessions.Cache.Client;
-using static VNLib.Plugins.Essentials.Oauth.OauthSessionExtensions;
-
-namespace VNLib.Plugins.Sessions.OAuth
-{
- /// <summary>
- /// Generates secure OAuth2 session tokens and initalizes new OAuth2 sessions
- /// </summary>
- internal class OAuth2SessionIdProvider : IOauthSessionIdFactory
- {
- private readonly string SessionIdPrefix;
- private readonly int _bufferSize;
- private readonly int _tokenSize;
-
- ///<inheritdoc/>
- public int MaxTokensPerApp { get; }
- ///<inheritdoc/>
- public TimeSpan SessionValidFor { get; }
- ///<inheritdoc/>
- string IOauthSessionIdFactory.TokenType => "Bearer";
-
- public OAuth2SessionIdProvider(string sessionIdPrefix, int maxTokensPerApp, int tokenSize, TimeSpan validFor)
- {
- SessionIdPrefix = sessionIdPrefix;
- MaxTokensPerApp = maxTokensPerApp;
- SessionValidFor = validFor;
- _tokenSize = tokenSize;
- _bufferSize = tokenSize * 2;
- }
-
- ///<inheritdoc/>
- bool IOauthSessionIdFactory.TryGetSessionId(IHttpEvent entity, [NotNullWhen(true)] out string? sessionId)
- {
- //Get authorization token and make sure its not too large to cause a buffer overflow
- if (entity.Server.HasAuthorization(out string? token) && (token.Length + SessionIdPrefix.Length) <= _bufferSize)
- {
- //Compute session id from token
- sessionId = ComputeSessionIdFromToken(token);
-
- return true;
- }
- else
- {
- sessionId = null;
- }
-
- return false;
- }
-
- private string ComputeSessionIdFromToken(string token)
- {
- //Buffer to copy data to
- using UnsafeMemoryHandle<char> buffer = MemoryUtil.UnsafeAlloc<char>(_bufferSize, true);
-
- //Writer to accumulate data
- ForwardOnlyWriter<char> writer = new(buffer.Span);
-
- //Append session id prefix and token
- writer.Append(SessionIdPrefix);
- writer.Append(token);
-
- //Compute base64 hash of token and
- return ManagedHash.ComputeBase64Hash(writer.AsSpan(), HashAlg.SHA256);
- }
-
- ///<inheritdoc/>
- TokenAndSessionIdResult IOauthSessionIdFactory.GenerateTokensAndId()
- {
- //Alloc buffer for random data
- using UnsafeMemoryHandle<byte> mem = MemoryUtil.UnsafeAlloc<byte>(_tokenSize, true);
-
- //Generate token from random cng bytes
- RandomHash.GetRandomBytes(mem);
-
- //Token is the raw value
- string token = Convert.ToBase64String(mem.Span);
-
- //The session id is the HMAC of the token
- string sessionId = ComputeSessionIdFromToken(token);
-
- //Clear buffer
- MemoryUtil.InitializeBlock(mem.Span);
-
- //Return sessid result
- return new(sessionId, token, null);
- }
-
- ///<inheritdoc/>
- void IOauthSessionIdFactory.InitNewSession(RemoteSession session, UserApplication app, IHttpEvent entity)
- {
- //Store session variables
- session[APP_ID_ENTRY] = app.Id;
- session[TOKEN_TYPE_ENTRY] = "client_credential,bearer";
- session[SCOPES_ENTRY] = app.Permissions;
- session.UserID = app.UserId;
- }
- }
-}
diff --git a/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionProvider.cs b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionProvider.cs
index 7047e6e..1797ddb 100644
--- a/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionProvider.cs
+++ b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionProvider.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Essentials.Sessions.OAuth
@@ -28,20 +28,18 @@ using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
-using Microsoft.EntityFrameworkCore;
-
using VNLib.Net.Http;
using VNLib.Utils;
using VNLib.Utils.Logging;
using VNLib.Data.Caching.Exceptions;
-using VNLib.Plugins.Sessions.Cache.Client;
using VNLib.Plugins.Essentials;
-using VNLib.Plugins.Essentials.Oauth;
using VNLib.Plugins.Essentials.Sessions;
using VNLib.Plugins.Essentials.Oauth.Tokens;
using VNLib.Plugins.Essentials.Oauth.Applications;
using VNLib.Plugins.Extensions.Loading;
+using VNLib.Plugins.Extensions.Loading.Sql;
using VNLib.Plugins.Extensions.Loading.Events;
+using static VNLib.Plugins.Essentials.Oauth.OauthSessionExtensions;
namespace VNLib.Plugins.Sessions.OAuth
{
@@ -49,139 +47,145 @@ namespace VNLib.Plugins.Sessions.OAuth
/// <summary>
/// Provides OAuth2 session management
/// </summary>
- [ConfigurationName("oauth2")]
- internal sealed class OAuth2SessionProvider : SessionCacheClient, ITokenManager, IIntervalScheduleable
- {
-
- private static readonly SessionHandle NotFoundHandle = new(null, FileProcessArgs.NotFound, null);
-
- private static readonly TimeSpan BackgroundTimeout = TimeSpan.FromSeconds(10);
+ [ConfigurationName(O2SessionProviderEntry.OAUTH2_CONFIG_KEY)]
+ internal sealed class OAuth2SessionProvider : ISessionProvider, ITokenManager, IApplicationTokenFactory
+ {
+ private static readonly SessionHandle Skip = new(null, FileProcessArgs.VirtualSkip, null);
-
- private readonly IOauthSessionIdFactory factory;
+ private readonly OAuth2SessionStore _sessions;
+ private readonly IOauthSessionIdFactory _tokenFactory;
private readonly TokenStore TokenStore;
- private readonly uint MaxConnections;
-
- public OAuth2SessionProvider(IRemoteCacheStore client, int maxCacheItems, uint maxConnections, IOauthSessionIdFactory idFactory, DbContextOptions dbCtx)
- : base(client, maxCacheItems)
+ private readonly string _tokenTypeString;
+ private readonly uint _maxConnections;
+
+ private uint _waitingConnections;
+
+ public bool IsConnected => _sessions.IsConnected;
+
+ public OAuth2SessionProvider(PluginBase plugin, IConfigScope config)
{
- factory = idFactory;
- TokenStore = new(dbCtx);
- MaxConnections = maxConnections;
+ _sessions = plugin.GetOrCreateSingleton<OAuth2SessionStore>();
+ _tokenFactory = plugin.GetOrCreateSingleton<OAuth2TokenFactory>();
+ TokenStore = new(plugin.GetContextOptions());
+ _tokenTypeString = $"client_credential,{_tokenFactory.TokenType}";
}
- ///<inheritdoc/>
- protected override RemoteSession SessionCtor(string sessionId) => new OAuth2Session(sessionId, Store, BackgroundTimeout, InvalidatateCache);
+ public void SetLog(ILogProvider log) => _sessions.SetLog(log);
- private void InvalidatateCache(OAuth2Session session)
+ public ValueTask<SessionHandle> GetSessionAsync(IHttpEvent entity, CancellationToken cancellationToken)
{
- lock (CacheLock)
+ //Limit max number of waiting clients and make sure were connected
+ if (!_sessions.IsConnected || _waitingConnections > _maxConnections)
{
- _ = CacheTable.Remove(session.SessionID);
+ //Set 503 for temporary unavail
+ entity.CloseResponse(HttpStatusCode.ServiceUnavailable);
+ return ValueTask.FromResult(Skip);
}
- }
-
- ///<inheritdoc/>
- public async ValueTask<SessionHandle> GetSessionAsync(IHttpEvent entity, CancellationToken cancellationToken)
- {
- //Callback to close the session when the handle is closeed
- static ValueTask HandleClosedAsync(ISession session, IHttpEvent entity)
+ ValueTask<OAuth2Session?> result = _sessions.GetSessionAsync(entity, cancellationToken);
+
+ if (result.IsCompleted)
{
- return ((SessionBase)session).UpdateAndRelease(true, entity);
+ OAuth2Session? session = result.GetAwaiter().GetResult();
+
+ //Post process and get handle for session
+ SessionHandle handle = PostProcess(session);
+
+ return ValueTask.FromResult(handle);
}
- try
+ else
{
- //Get session id
- if (!factory.TryGetSessionId(entity, out string? sessionId))
- {
- //Id not allowed/found, so do not attach a session
- return SessionHandle.Empty;
- }
+ return new(AwaitAsyncGet(result));
+ }
+ }
- //Limit max number of waiting clients
- if (!IsConnected || WaitingConnections > MaxConnections)
- {
- //Set 503 for temporary unavail
- entity.CloseResponse(HttpStatusCode.ServiceUnavailable);
- return new SessionHandle(null, FileProcessArgs.VirtualSkip, null);
- }
+ private async Task<SessionHandle> AwaitAsyncGet(ValueTask<OAuth2Session?> async)
+ {
+ //Inct wait count while async waiting
+ _waitingConnections++;
+ try
+ {
+ //await the session
+ OAuth2Session? session = await async.ConfigureAwait(false);
- //Recover the session
- RemoteSession session = await base.GetSessionAsync(entity, sessionId, cancellationToken);
-
- //Session should not be new
- if (session.IsNew)
- {
- //Invalidate the session, so it is deleted
- session.Invalidate();
- await session.UpdateAndRelease(true, entity);
- return SessionHandle.Empty;
- }
- //Make sure session is still valid
- if (session.Created.Add(factory.SessionValidFor) < DateTimeOffset.UtcNow)
- {
- //Invalidate the handle
- session.Invalidate();
- //Flush changes
- await session.UpdateAndRelease(false, entity);
- //Remove the token from the db backing store
- await TokenStore.RevokeTokenAsync(sessionId, cancellationToken);
- //close entity
- entity.CloseResponseError(HttpStatusCode.Unauthorized, ErrorType.InvalidToken, "The token has expired");
- //return a completed handle
- return NotFoundHandle;
- }
-
- return new SessionHandle(session, HandleClosedAsync);
+ //return empty session handle if the session could not be found
+ return PostProcess(session);
}
- //Pass session exceptions
- catch (SessionException)
+ finally
{
- throw;
+ _waitingConnections--;
}
- catch (Exception ex)
+ }
+
+ private SessionHandle PostProcess(OAuth2Session? session)
+ {
+ if (session == null)
{
- throw new SessionException("Exception raised while retreiving or loading OAuth2 session", ex);
+ return SessionHandle.Empty;
+ }
+
+ //Make sure the session has not expired yet
+ if (session.Created.Add(_tokenFactory.SessionValidFor) < DateTimeOffset.UtcNow)
+ {
+ //Invalidate the session, so its technically valid for this request, but will be cleared on this handle close cycle
+ session.Invalidate();
+
+ //Clears important security variables
+ InitNewSession(session, null);
}
+
+ return new SessionHandle(session, OnSessionReleases);
}
+
+ private ValueTask OnSessionReleases(ISession session, IHttpEvent entity) => _sessions.ReleaseSessionAsync((OAuth2Session)session, entity);
+
///<inheritdoc/>
public async Task<IOAuth2TokenResult?> CreateAccessTokenAsync(IHttpEvent ev, UserApplication app, CancellationToken cancellation)
{
//Get a new session for the current connection
- TokenAndSessionIdResult ids = factory.GenerateTokensAndId();
+ GetTokenResult ids = _tokenFactory.GenerateTokensAndId();
+
//try to insert token into the store, may fail if max has been reached
- if (await TokenStore.InsertTokenAsync(ids.SessionId, app.Id!, ids.RefreshToken, factory.MaxTokensPerApp, cancellation) != ERRNO.SUCCESS)
+ if (await TokenStore.InsertTokenAsync(ids.AccessToken, app.Id!, ids.RefreshToken, _tokenFactory.MaxTokensPerApp, cancellation) != ERRNO.SUCCESS)
{
return null;
}
- //Create new session from the session id
- RemoteSession session = SessionCtor(ids.SessionId);
- await session.WaitAndLoadAsync(ev, cancellation);
- try
- {
- //Init new session
- factory.InitNewSession(session, app, ev);
- }
- finally
- {
- await session.UpdateAndRelease(false, ev);
- }
+
+ //Create new session
+ OAuth2Session newSession = _sessions.CreateSession(ev, ids.AccessToken);
+
+ //Init the new session with application information
+ InitNewSession(newSession, app);
+
+ //Commit the new session
+ await _sessions.CommitSessionAsync(newSession);
+
//Init new token result to pass to client
return new OAuth2TokenResult()
{
- ExpiresSeconds = (int)factory.SessionValidFor.TotalSeconds,
- TokenType = factory.TokenType,
+ ExpiresSeconds = (int)_tokenFactory.SessionValidFor.TotalSeconds,
+ TokenType = _tokenFactory.TokenType,
//Return token and refresh token
AccessToken = ids.AccessToken,
RefreshToken = ids.RefreshToken,
};
}
+
+ private void InitNewSession(OAuth2Session session, UserApplication? app)
+ {
+ //Store session variables
+ session[APP_ID_ENTRY] = app?.Id;
+ session[TOKEN_TYPE_ENTRY] = _tokenTypeString;
+ session[SCOPES_ENTRY] = app?.Permissions;
+ session.UserID = app?.UserId;
+ }
+
///<inheritdoc/>
Task ITokenManager.RevokeTokensAsync(IReadOnlyCollection<string> tokens, CancellationToken cancellation)
{
return TokenStore.RevokeTokensAsync(tokens, cancellation);
}
+
///<inheritdoc/>
Task ITokenManager.RevokeTokensForAppAsync(string appId, CancellationToken cancellation)
{
@@ -193,11 +197,11 @@ namespace VNLib.Plugins.Sessions.OAuth
* Interval for removing expired tokens
*/
- ///<inheritdoc/>
- async Task IIntervalScheduleable.OnIntervalAsync(ILogProvider log, CancellationToken cancellationToken)
+ [AsyncInterval(Minutes = 2)]
+ private async Task OnIntervalAsync(ILogProvider log, CancellationToken cancellationToken)
{
//Calculate valid token time
- DateTime validAfter = DateTime.UtcNow.Subtract(factory.SessionValidFor);
+ DateTime validAfter = DateTime.UtcNow.Subtract(_tokenFactory.SessionValidFor);
//Remove tokens from db store
IReadOnlyCollection<ActiveToken> revoked = await TokenStore.CleanupExpiredTokensAsync(validAfter, cancellationToken);
//exception list
@@ -208,17 +212,17 @@ namespace VNLib.Plugins.Sessions.OAuth
try
{
//Remove tokens by thier object id from cache
- await base.Store.DeleteObjectAsync(token.Id, cancellationToken);
+ await _sessions.DeleteTokenAsync(token.Id, cancellationToken);
}
//Ignore if the object has already been removed
catch (ObjectNotFoundException)
{}
catch (Exception ex)
{
- errors = new()
- {
- ex
- };
+#pragma warning disable CA1508 // Avoid dead conditional code
+ errors ??= new();
+#pragma warning restore CA1508 // Avoid dead conditional code
+ errors.Add(ex);
}
}
if (errors?.Count > 0)
diff --git a/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionStore.cs b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionStore.cs
new file mode 100644
index 0000000..8719002
--- /dev/null
+++ b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionStore.cs
@@ -0,0 +1,101 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.Sessions.OAuth
+* File: OAuth2SessionStore.cs
+*
+* OAuth2SessionStore.cs is part of VNLib.Plugins.Essentials.Sessions.OAuth which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Essentials.Sessions.OAuth 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.Sessions.OAuth 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.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
+using VNLib.Hashing;
+using VNLib.Net.Http;
+using VNLib.Utils.Logging;
+using VNLib.Data.Caching;
+using VNLib.Plugins.Sessions.Cache.Client;
+using VNLib.Plugins.Extensions.VNCache;
+using VNLib.Plugins.Extensions.Loading;
+
+
+namespace VNLib.Plugins.Sessions.OAuth
+{
+ [ConfigurationName(O2SessionProviderEntry.OAUTH2_CONFIG_KEY)]
+ internal sealed class OAuth2SessionStore : SessionStore<OAuth2Session>
+ {
+ private ILogProvider _log;
+
+ protected override ISessionIdFactory IdFactory { get; }
+ protected override IRemoteCacheStore Cache { get; }
+ protected override ISessionFactory<OAuth2Session> SessionFactory { get; }
+ protected override ILogProvider Log => _log;
+
+ public bool IsConnected => Cache.IsConnected;
+
+ public OAuth2SessionStore(PluginBase plugin, IConfigScope config)
+ {
+ OAuth2SessionConfig o2Conf = config.DeserialzeAndValidate<OAuth2SessionConfig>();
+
+ //Get global cache
+ IGlobalCacheProvider cache = plugin.GetOrCreateSingleton<VnGlobalCache>()
+ .GetPrefixedCache(o2Conf.CachePrefix, HashAlg.SHA256);
+
+ //Create remote cache
+ Cache = new GlobalCacheStore(cache);
+
+ IdFactory = plugin.GetOrCreateSingleton<OAuth2TokenFactory>();
+
+ SessionFactory = new OAuth2SessionFactory();
+
+ //Default to plugin cache
+ _log = plugin.Log;
+ }
+
+ public void SetLog(ILogProvider log)
+ {
+ _log = log;
+ }
+
+
+ public Task DeleteTokenAsync(string token, CancellationToken cancellation)
+ {
+ return Cache.DeleteObjectAsync(token, cancellation);
+ }
+
+ public OAuth2Session CreateSession(IHttpEvent entity, string sessionId)
+ {
+ //Get the new session
+ OAuth2Session session = SessionFactory.GetNewSession(entity, sessionId, new Dictionary<string, string>(10));
+ //Configure the new session for use
+ session.InitNewSession(entity);
+
+ return session;
+ }
+
+ public async Task CommitSessionAsync(OAuth2Session session)
+ {
+ IDictionary<string, string> sessionData = session.GetSessionData();
+ //Write data to cache
+ await Cache.AddOrUpdateObjectAsync(session.SessionID, null, sessionData);
+ //Good programming, update session
+ session.SessionUpdateComplete();
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2TokenFactory.cs b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2TokenFactory.cs
new file mode 100644
index 0000000..b452e29
--- /dev/null
+++ b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2TokenFactory.cs
@@ -0,0 +1,99 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.Sessions.OAuth
+* File: OAuth2TokenFactory.cs
+*
+* OAuth2TokenFactory.cs is part of VNLib.Plugins.Essentials.Sessions.OAuth which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Essentials.Sessions.OAuth 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.Sessions.OAuth 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.Hashing;
+using VNLib.Net.Http;
+using VNLib.Plugins.Sessions.Cache.Client;
+using VNLib.Plugins.Extensions.Loading;
+using VNLib.Plugins.Essentials.Extensions;
+
+
+namespace VNLib.Plugins.Sessions.OAuth
+{
+ [ConfigurationName(O2SessionProviderEntry.OAUTH2_CONFIG_KEY)]
+ internal sealed class OAuth2TokenFactory : ISessionIdFactory, IOauthSessionIdFactory
+ {
+ private readonly OAuth2SessionConfig _config;
+
+ public OAuth2TokenFactory(PluginBase plugin, IConfigScope config)
+ {
+ //Get the oauth2 config
+ _config = config.DeserialzeAndValidate<OAuth2SessionConfig>();
+ }
+
+ /*
+ * ID Regeneration is always false as OAuth2 sessions
+ * do not allow dynamic ID updates, they require a
+ * negotiation
+ */
+
+ bool ISessionIdFactory.RegenerationSupported => false;
+
+ /*
+ * Connections that do not identify themselves, via a token are
+ * not valid. ID/Tokens must be created at once during
+ * authentication stage.
+ */
+
+ bool ISessionIdFactory.RegenIdOnEmptyEntry => false;
+
+
+ ///<inheritdoc/>
+ int IOauthSessionIdFactory.MaxTokensPerApp => _config.MaxTokensPerApp;
+
+ ///<inheritdoc/>
+ TimeSpan IOauthSessionIdFactory.SessionValidFor => TimeSpan.FromSeconds(_config.TokenLifeTimeSeconds);
+
+ ///<inheritdoc/>
+ string IOauthSessionIdFactory.TokenType => "Bearer";
+
+ ///<inheritdoc/>
+ bool ISessionIdFactory.CanService(IHttpEvent entity)
+ {
+ return entity.Server.HasAuthorization(out _);
+ }
+
+ ///<inheritdoc/>
+ public GetTokenResult GenerateTokensAndId()
+ {
+ //Token is the raw value
+ string token = RandomHash.GetRandomBase64(_config.AccessTokenSize);
+
+ //Return sessid result
+ return new(token, null);
+ }
+
+ string ISessionIdFactory.RegenerateId(IHttpEvent entity)
+ {
+ throw new NotSupportedException("Id regeneration is not supported for OAuth2 sessions");
+ }
+
+ string? ISessionIdFactory.TryGetSessionId(IHttpEvent entity)
+ {
+ return entity.Server.HasAuthorization(out string? token) ? token : null;
+ }
+ }
+} \ No newline at end of file