Skip to content

Commit

Permalink
Merge pull request #2138 from sbwalker/dev
Browse files Browse the repository at this point in the history
create separate API methods for tokens (short-lived) and personal access tokens (long-lived), include global antiforgery filter to mitigate XSRF when using cookie auth (ignored when using Jwt)
  • Loading branch information
sbwalker authored Apr 14, 2022
2 parents c616878 + f6b3874 commit 68d9ac8
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 6 deletions.
2 changes: 1 addition & 1 deletion Oqtane.Client/Modules/Admin/Users/Index.razor
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ else

private async Task CreateToken()
{
_token = await UserService.GetTokenAsync();
_token = await UserService.GetPersonalAccessTokenAsync();
}

private void ToggleClientSecret()
Expand Down
6 changes: 6 additions & 0 deletions Oqtane.Client/Services/Interfaces/IUserService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,11 @@ public interface IUserService
/// </summary>
/// <returns></returns>
Task<string> GetTokenAsync();

/// <summary>
/// Get personal access token for current user (administrators only)
/// </summary>
/// <returns></returns>
Task<string> GetPersonalAccessTokenAsync();
}
}
5 changes: 5 additions & 0 deletions Oqtane.Client/Services/UserService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,10 @@ public async Task<string> GetTokenAsync()
{
return await GetStringAsync($"{Apiurl}/token");
}

public async Task<string> GetPersonalAccessTokenAsync()
{
return await GetStringAsync($"{Apiurl}/personalaccesstoken");
}
}
}
17 changes: 16 additions & 1 deletion Oqtane.Server/Controllers/UserController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -522,8 +522,23 @@ public async Task<bool> Validate(string password)

// GET api/<controller>/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/<controller>/personalaccesstoken
[HttpGet("personalaccesstoken")]
[Authorize(Roles = RoleNames.Admin)]
public string PersonalAccessToken()
{
var token = "";
var sitesettings = HttpContext.GetSiteSettings();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ internal static IServiceCollection AddOqtaneSingletonServices(this IServiceColle
services.AddSingleton<IDatabaseManager, DatabaseManager>();
services.AddSingleton<IConfigManager, ConfigManager>();
services.AddSingleton<ILoggerProvider, FileLoggerProvider>();
services.AddSingleton<AutoValidateAntiforgeryTokenFilter>();
return services;
}

Expand Down
1 change: 1 addition & 0 deletions Oqtane.Server/Oqtane.Server.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="6.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.ViewFeatures" Version="2.2.0" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="4.1.0" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="6.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.3" />
Expand Down
19 changes: 19 additions & 0 deletions Oqtane.Server/Security/AutoValidateAntiforgeryTokenAttribute.cs
Original file line number Diff line number Diff line change
@@ -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<AutoValidateAntiforgeryTokenFilter>();
}
}
}
64 changes: 64 additions & 0 deletions Oqtane.Server/Security/AutoValidateAntiforgeryTokenFilter.cs
Original file line number Diff line number Diff line change
@@ -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<IAntiforgeryPolicy>(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;
}
}
}
11 changes: 7 additions & 4 deletions Oqtane.Server/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
{
Expand Down

0 comments on commit 68d9ac8

Please sign in to comment.