diff --git a/Oqtane.Client/Modules/Admin/Users/Index.razor b/Oqtane.Client/Modules/Admin/Users/Index.razor
index eb66f772b..50fc9d3f3 100644
--- a/Oqtane.Client/Modules/Admin/Users/Index.razor
+++ b/Oqtane.Client/Modules/Admin/Users/Index.razor
@@ -575,7 +575,7 @@ else
private async Task CreateToken()
{
- _token = await UserService.GetTokenAsync();
+ _token = await UserService.GetPersonalAccessTokenAsync();
}
private void ToggleClientSecret()
diff --git a/Oqtane.Client/Services/Interfaces/IUserService.cs b/Oqtane.Client/Services/Interfaces/IUserService.cs
index 9a80c7bde..a3f51ebf6 100644
--- a/Oqtane.Client/Services/Interfaces/IUserService.cs
+++ b/Oqtane.Client/Services/Interfaces/IUserService.cs
@@ -109,5 +109,11 @@ public interface IUserService
///
///
Task GetTokenAsync();
+
+ ///
+ /// Get personal access token for current user (administrators only)
+ ///
+ ///
+ Task GetPersonalAccessTokenAsync();
}
}
diff --git a/Oqtane.Client/Services/UserService.cs b/Oqtane.Client/Services/UserService.cs
index 625d5cd1a..c0b25edc8 100644
--- a/Oqtane.Client/Services/UserService.cs
+++ b/Oqtane.Client/Services/UserService.cs
@@ -84,5 +84,10 @@ public async Task GetTokenAsync()
{
return await GetStringAsync($"{Apiurl}/token");
}
+
+ public async Task GetPersonalAccessTokenAsync()
+ {
+ return await GetStringAsync($"{Apiurl}/personalaccesstoken");
+ }
}
}
diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs
index b741cae08..c7743fb8c 100644
--- a/Oqtane.Server/Controllers/UserController.cs
+++ b/Oqtane.Server/Controllers/UserController.cs
@@ -522,8 +522,23 @@ public async Task Validate(string password)
// GET api//token
[HttpGet("token")]
- [Authorize(Roles = RoleNames.Admin)]
+ [Authorize(Roles = RoleNames.Registered)]
public string Token()
+ {
+ var token = "";
+ var sitesettings = HttpContext.GetSiteSettings();
+ var secret = sitesettings.GetValue("JwtOptions:Secret", "");
+ if (!string.IsNullOrEmpty(secret))
+ {
+ token = _jwtManager.GenerateToken(_tenantManager.GetAlias(), (ClaimsIdentity)User.Identity, secret, sitesettings.GetValue("JwtOptions:Issuer", ""), sitesettings.GetValue("JwtOptions:Audience", ""), int.Parse(sitesettings.GetValue("JwtOptions:Audience", "20")));
+ }
+ return token;
+ }
+
+ // GET api//personalaccesstoken
+ [HttpGet("personalaccesstoken")]
+ [Authorize(Roles = RoleNames.Admin)]
+ public string PersonalAccessToken()
{
var token = "";
var sitesettings = HttpContext.GetSiteSettings();
diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs
index 7377abfa0..31132a00c 100644
--- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs
+++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs
@@ -72,6 +72,7 @@ internal static IServiceCollection AddOqtaneSingletonServices(this IServiceColle
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
+ services.AddSingleton();
return services;
}
diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj
index fd28d904d..d8b4cfb07 100644
--- a/Oqtane.Server/Oqtane.Server.csproj
+++ b/Oqtane.Server/Oqtane.Server.csproj
@@ -36,6 +36,7 @@
+
diff --git a/Oqtane.Server/Security/AutoValidateAntiforgeryTokenAttribute.cs b/Oqtane.Server/Security/AutoValidateAntiforgeryTokenAttribute.cs
new file mode 100644
index 000000000..56bf4991a
--- /dev/null
+++ b/Oqtane.Server/Security/AutoValidateAntiforgeryTokenAttribute.cs
@@ -0,0 +1,19 @@
+using System;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Oqtane.Security
+{
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public class AutoValidateAntiforgeryTokenAttribute : Attribute, IFilterFactory, IOrderedFilter
+ {
+ public int Order { get; set; } = 1000;
+
+ public bool IsReusable => true;
+
+ public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
+ {
+ return serviceProvider.GetRequiredService();
+ }
+ }
+}
diff --git a/Oqtane.Server/Security/AutoValidateAntiforgeryTokenFilter.cs b/Oqtane.Server/Security/AutoValidateAntiforgeryTokenFilter.cs
new file mode 100644
index 000000000..9cbf466c9
--- /dev/null
+++ b/Oqtane.Server/Security/AutoValidateAntiforgeryTokenFilter.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Antiforgery;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.AspNetCore.Mvc.ViewFeatures;
+
+namespace Oqtane.Security
+{
+ public class AutoValidateAntiforgeryTokenFilter : IAsyncAuthorizationFilter, IAntiforgeryPolicy
+ {
+ private readonly IAntiforgery _antiforgery;
+
+ public AutoValidateAntiforgeryTokenFilter(IAntiforgery antiforgery)
+ {
+ _antiforgery = antiforgery;
+ }
+
+ public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ if (!context.IsEffectivePolicy(this))
+ {
+ return;
+ }
+
+ if (ShouldValidate(context))
+ {
+ try
+ {
+ await _antiforgery.ValidateRequestAsync(context.HttpContext);
+ }
+ catch
+ {
+ context.Result = new AntiforgeryValidationFailedResult();
+ }
+ }
+ }
+
+ protected virtual bool ShouldValidate(AuthorizationFilterContext context)
+ {
+ // ignore antiforgery validation if a bearer token was provided
+ if (context.HttpContext.Request.Headers.ContainsKey("Authorization"))
+ {
+ return false;
+ }
+
+ // ignore antiforgery validation for GET, HEAD, TRACE, OPTIONS
+ var method = context.HttpContext.Request.Method;
+ if (HttpMethods.IsGet(method) || HttpMethods.IsHead(method) || HttpMethods.IsTrace(method) || HttpMethods.IsOptions(method))
+ {
+ return false;
+ }
+
+ // everything else requires antiforgery validation (ie. POST, PUT, DELETE)
+ return true;
+ }
+ }
+}
diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs
index c1a2aeb2d..afadb1e42 100644
--- a/Oqtane.Server/Startup.cs
+++ b/Oqtane.Server/Startup.cs
@@ -128,10 +128,13 @@ public void ConfigureServices(IServiceCollection services)
services.AddOqtaneAuthorizationPolicies();
- services.AddMvc()
- .AddNewtonsoftJson()
- .AddOqtaneApplicationParts() // register any Controllers from custom modules
- .ConfigureOqtaneMvc(); // any additional configuration from IStartup classes
+ services.AddMvc(options =>
+ {
+ options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
+ })
+ .AddNewtonsoftJson()
+ .AddOqtaneApplicationParts() // register any Controllers from custom modules
+ .ConfigureOqtaneMvc(); // any additional configuration from IStartup classes
services.AddSwaggerGen(options =>
{