diff --git a/Directory.Packages.props b/Directory.Packages.props
index 7510cde91..b9e3eb784 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -31,7 +31,6 @@
-
diff --git a/Elsa.sln b/Elsa.sln
index d67f34f16..a6d64300a 100644
--- a/Elsa.sln
+++ b/Elsa.sln
@@ -260,6 +260,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Expressions.Liquid", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Expressions.Python", "src\modules\Elsa.Expressions.Python\Elsa.Expressions.Python.csproj", "{9D8FB664-88B4-10BE-58A2-D9A1644AD2E4}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.SasTokens", "src\modules\Elsa.SasTokens\Elsa.SasTokens.csproj", "{A7DE02E3-405B-B6BA-7E47-9E27D4BEFB24}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -444,6 +446,10 @@ Global
{9D8FB664-88B4-10BE-58A2-D9A1644AD2E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9D8FB664-88B4-10BE-58A2-D9A1644AD2E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9D8FB664-88B4-10BE-58A2-D9A1644AD2E4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A7DE02E3-405B-B6BA-7E47-9E27D4BEFB24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A7DE02E3-405B-B6BA-7E47-9E27D4BEFB24}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A7DE02E3-405B-B6BA-7E47-9E27D4BEFB24}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A7DE02E3-405B-B6BA-7E47-9E27D4BEFB24}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -520,6 +526,7 @@ Global
{08B69CC4-B5F0-44E8-FA7A-6BF6F00CA40A} = {6EF07978-A6D2-40EB-891D-7D70C5F37E76}
{6AF53651-99F0-1DE0-D37B-4FF6B0348DBB} = {6EF07978-A6D2-40EB-891D-7D70C5F37E76}
{9D8FB664-88B4-10BE-58A2-D9A1644AD2E4} = {6EF07978-A6D2-40EB-891D-7D70C5F37E76}
+ {A7DE02E3-405B-B6BA-7E47-9E27D4BEFB24} = {5948B0A5-7873-4DBB-BA03-EB283D6EA91B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D4B5CEAA-7D70-4FCB-A68E-B03FBE5E0E5E}
diff --git a/src/modules/Elsa.Http/Elsa.Http.csproj b/src/modules/Elsa.Http/Elsa.Http.csproj
index c6c6b1037..810b479a0 100644
--- a/src/modules/Elsa.Http/Elsa.Http.csproj
+++ b/src/modules/Elsa.Http/Elsa.Http.csproj
@@ -9,7 +9,6 @@
-
@@ -22,6 +21,7 @@
+
diff --git a/src/modules/Elsa.SasTokens/Contracts/DataProtectorTokenService.cs b/src/modules/Elsa.SasTokens/Contracts/DataProtectorTokenService.cs
new file mode 100644
index 000000000..df79897c5
--- /dev/null
+++ b/src/modules/Elsa.SasTokens/Contracts/DataProtectorTokenService.cs
@@ -0,0 +1,66 @@
+using System.Text.Json;
+using Microsoft.AspNetCore.DataProtection;
+
+namespace Elsa.SasTokens.Contracts;
+
+///
+/// A service that can create and decrypt SAS (Shared Access Signature) tokens using the service.
+///
+public class DataProtectorTokenService : ITokenService
+{
+ private readonly IDataProtector _dataProtector;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public DataProtectorTokenService(IDataProtectionProvider dataProtector)
+ {
+ _dataProtector = dataProtector.CreateProtector("Elsa Tokens");
+ }
+
+ ///
+ public string CreateToken(T payload, TimeSpan lifetime)
+ {
+ var json = JsonSerializer.Serialize(payload);
+ return _dataProtector.ToTimeLimitedDataProtector().Protect(json, lifetime);
+ }
+
+ ///
+ public string CreateToken(T payload, DateTimeOffset expiresAt)
+ {
+ var json = JsonSerializer.Serialize(payload);
+ return _dataProtector.ToTimeLimitedDataProtector().Protect(json, expiresAt);
+ }
+
+ ///
+ public string CreateToken(T payload)
+ {
+ var json = JsonSerializer.Serialize(payload);
+ return _dataProtector.Protect(json);
+ }
+
+ ///
+ public bool TryDecryptToken(string token, out T payload)
+ {
+ payload = default!;
+
+ try
+ {
+ payload = DecryptToken(token);
+ return true;
+ }
+ catch
+ {
+ // ignored.
+ }
+
+ return false;
+ }
+
+ ///
+ public T DecryptToken(string token)
+ {
+ var json = _dataProtector.Unprotect(token);
+ return JsonSerializer.Deserialize(json)!;
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.SasTokens/Contracts/ITokenService.cs b/src/modules/Elsa.SasTokens/Contracts/ITokenService.cs
new file mode 100644
index 000000000..8d489d555
--- /dev/null
+++ b/src/modules/Elsa.SasTokens/Contracts/ITokenService.cs
@@ -0,0 +1,32 @@
+namespace Elsa.SasTokens.Contracts;
+
+///
+/// A service that can create and decrypt SAS (Shared Access Signature) tokens.
+///
+public interface ITokenService
+{
+ ///
+ /// Creates a SAS (Shared Access Signature) token containing the specified data.
+ ///
+ string CreateToken(T payload, TimeSpan lifetime);
+
+ ///
+ /// Creates a SAS (Shared Access Signature) token containing the specified data.
+ ///
+ string CreateToken(T payload, DateTimeOffset expiresAt);
+
+ ///
+ /// Creates a SAS (Shared Access Signature) token containing the specified data.
+ ///
+ string CreateToken(T payload);
+
+ ///
+ /// Decrypts the specified SAS token.
+ ///
+ T DecryptToken(string token);
+
+ ///
+ /// Decrypts the specified SAS token.
+ ///
+ bool TryDecryptToken(string token, out T payload);
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.SasTokens/Elsa.SasTokens.csproj b/src/modules/Elsa.SasTokens/Elsa.SasTokens.csproj
new file mode 100644
index 000000000..eb5ea46cf
--- /dev/null
+++ b/src/modules/Elsa.SasTokens/Elsa.SasTokens.csproj
@@ -0,0 +1,17 @@
+
+
+
+
+ Provides services to generate SAS tokens.
+
+ elsa module security sas tokens
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/Elsa.SasTokens/Extensions/ModuleExtensions.cs b/src/modules/Elsa.SasTokens/Extensions/ModuleExtensions.cs
new file mode 100644
index 000000000..9c42b9c45
--- /dev/null
+++ b/src/modules/Elsa.SasTokens/Extensions/ModuleExtensions.cs
@@ -0,0 +1,20 @@
+using Elsa.Features.Services;
+using Elsa.SasTokens.Features;
+
+// ReSharper disable once CheckNamespace
+namespace Elsa.Extensions;
+
+///
+/// Provides extensions to install the feature.
+///
+public static class ModuleExtensions
+{
+ ///
+ /// Install the feature.
+ ///
+ public static IModule UseSasTokens(this IModule module, Action? configure = default)
+ {
+ module.Configure(configure);
+ return module;
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.SasTokens/Features/SasTokensFeature.cs b/src/modules/Elsa.SasTokens/Features/SasTokensFeature.cs
new file mode 100644
index 000000000..2678766e1
--- /dev/null
+++ b/src/modules/Elsa.SasTokens/Features/SasTokensFeature.cs
@@ -0,0 +1,35 @@
+using Elsa.Features.Abstractions;
+using Elsa.Features.Services;
+using Elsa.SasTokens.Contracts;
+using Microsoft.AspNetCore.DataProtection;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Elsa.SasTokens.Features;
+
+///
+/// Adds the SAS tokens feature to the workflow runtime.
+///
+///
+public class SasTokensFeature(IModule module) : FeatureBase(module)
+{
+ ///
+ /// Configures the used for setting up data protection.
+ /// Defaults to setting the application name to "Elsa Workflows".
+ ///
+ public Action ConfigureDataProtectionBuilder { get; set; } = b => { b.SetApplicationName("Elsa Workflows"); };
+
+ ///
+ /// Factory method to create an instance of .
+ /// Defaults to creating a using dependency injection.
+ ///
+ public Func TokenService { get; set; } = sp => ActivatorUtilities.CreateInstance(sp);
+
+ ///
+ public override void Apply()
+ {
+ var builder = Services.AddDataProtection();
+ ConfigureDataProtectionBuilder(builder);
+
+ Services.AddScoped(TokenService);
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.SasTokens/FodyWeavers.xml b/src/modules/Elsa.SasTokens/FodyWeavers.xml
new file mode 100644
index 000000000..00e1d9a1c
--- /dev/null
+++ b/src/modules/Elsa.SasTokens/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file