Authentication finally moved to the dotnet way in webapi, ready to be added to to deal with users and such

Introspection access point properly uses basic auth of client id and secret to access
This commit is contained in:
2021-10-14 20:54:58 -04:00
parent 0dffa6e516
commit 320d939c76
9 changed files with 176 additions and 19 deletions

View File

@@ -7,7 +7,7 @@
<ServerCreationForm/> <ServerCreationForm/>
@code { @code {
protected async override Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
base.OnInitializedAsync(); base.OnInitializedAsync();
} }

View File

@@ -0,0 +1,64 @@
using System;
using System.Linq;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
using WebAPI.Data;
namespace WebAPI.Auth
{
public class OIDCAuthorization : ServiceFilterAttribute
{
public OIDCAuthorization() : base(typeof(CustomAuthorizationFilter))
{
}
}
/// <summary>
/// Authorization filter for checking the bearer token against the OIDC service.
/// Will short circuit on an invalid token
///
/// </summary>
public class CustomAuthorizationFilter: IAsyncAuthorizationFilter
{
private readonly ILogger<CustomAuthorizationFilter> _logger;
private readonly OIDCService _oidcService;
private readonly AppDbContext _appDbContext;
public CustomAuthorizationFilter(ILogger<CustomAuthorizationFilter> logger, OIDCService oidcService, AppDbContext appDbContext)
{
_logger = logger;
_oidcService = oidcService;
_appDbContext = appDbContext;
}
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
try
{
var httpContext = context.HttpContext;
var bearerToken = httpContext?.Request?.Headers[HeaderNames.Authorization].FirstOrDefault();
if (string.IsNullOrEmpty(bearerToken))
{
throw new Exception("Need a token");
}
string token = bearerToken.Split(" ").ElementAt(1);
if (!await _oidcService.ValidateAccessToken(token))
{
throw new Exception("bad token");
}
}
catch (Exception e)
{
context.Result = new ForbidResult(e.Message);
}
}
}
}

View File

@@ -0,0 +1,58 @@
using System.Linq;
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using WebAPI.Data;
namespace WebAPI.Auth
{
public class OIDCTokenAuthenticationHandler : AuthenticationHandler<OIDCTokenAuthenticationOptions>
{
private readonly OIDCService _oidcService;
public OIDCTokenAuthenticationHandler(IOptionsMonitor<OIDCTokenAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, OIDCService oidcService) : base(options, logger, encoder, clock)
{
_oidcService = oidcService;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var bearerToken = Request.Headers[HeaderNames.Authorization].FirstOrDefault() ?? "";
if (string.IsNullOrEmpty(bearerToken))
{
return AuthenticateResult.Fail("no token");
}
var token = bearerToken.Split(" ").ElementAt(1);
if (!await _oidcService.ValidateAccessToken(token))
{
return AuthenticateResult.Fail("failed to validate token");
}
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Authentication, token)
}, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
return AuthenticateResult.Success(new AuthenticationTicket(principal, Scheme.Name));
}
}
public class OIDCTokenAuthenticationOptions : AuthenticationSchemeOptions
{
public string OIDCIntrospectionEndpoint { get; set; }
public string OIDCClientId { get; set; }
public string OIDCClientSecret { get; set; }
}
public class OIDCTokenAuthenticationDefaults
{
public static string DefaultScheme => "OIDCAuthentication";
}
}

View File

@@ -2,33 +2,30 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using WebAPI.Auth;
using WebAPI.Data; using WebAPI.Data;
namespace WebAPI.Controllers namespace WebAPI.Controllers
{ {
[Authorize(Policy = "test")]
[Route("api/[controller]")] [Route("api/[controller]")]
[ApiController] [ApiController]
public class HelloWorldController : BaseController public class HelloWorldController : BaseController
{ {
private readonly PterodactylService _pterodactylService; private readonly PterodactylService _pterodactylService;
private readonly OIDCService _oidcService;
public HelloWorldController(PterodactylService pterodactylService, OIDCService oidcService) public HelloWorldController(PterodactylService pterodactylService)
{ {
_pterodactylService = pterodactylService; _pterodactylService = pterodactylService;
_oidcService = oidcService;
} }
[HttpGet] [HttpGet]
public async Task<string> HelloWorld() public async Task<string> HelloWorld()
{ {
if (await _oidcService.ValidateAccessToken(BearerToken)) return "Success";
{
return "Validated";
}
return "Failed";
} }
} }

View File

@@ -7,7 +7,9 @@ namespace WebAPI.Data
{ {
public static string PterodactylAPIKey { get; private set; } public static string PterodactylAPIKey { get; private set; }
public static string PterodactylPanelURL { get; private set; } public static string PterodactylPanelURL { get; private set; }
public static string OIDCUserInfoEndpoint { get; private set; } public static string OIDCIntrospectionEndpoint { get; private set; }
public static string OIDCClientId { get; private set; }
public static string OIDCClientSecret { get; set; }
public static void Init(IConfiguration configuration) public static void Init(IConfiguration configuration)
{ {
var fields = typeof(AppSettings).GetProperties(); var fields = typeof(AppSettings).GetProperties();

View File

@@ -1,14 +1,21 @@
using System; using System;
using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace WebAPI.Data namespace WebAPI.Data
{ {
public class OIDCService public class OIDCService
{ {
protected class IntrospectionResponse
{
public bool Active { get; set; }
}
private HttpClient _httpClient { get; set; } private HttpClient _httpClient { get; set; }
private ILogger<OIDCService> _logger { get; set; } private ILogger<OIDCService> _logger { get; set; }
public OIDCService(ILogger<OIDCService> logger) public OIDCService(ILogger<OIDCService> logger)
@@ -24,15 +31,23 @@ namespace WebAPI.Data
/// <returns>success</returns> /// <returns>success</returns>
public async Task<bool> ValidateAccessToken(string accessToken) public async Task<bool> ValidateAccessToken(string accessToken)
{ {
Uri requestUri = new Uri($"{AppSettings.OIDCUserInfoEndpoint}"); Uri requestUri = new Uri($"https://{AppSettings.OIDCIntrospectionEndpoint}");
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUri); HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, requestUri);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); request.Content = new FormUrlEncodedContent(new Dictionary<string, string>()
{
{"token", accessToken}
});
string encodedAuth = Convert.ToBase64String(Encoding.GetEncoding(Encoding.Latin1.CodePage)
.GetBytes($"{AppSettings.OIDCClientId}:{AppSettings.OIDCClientSecret}"));
request.Headers.Authorization = new AuthenticationHeaderValue("Basic", encodedAuth);
HttpResponseMessage response = await _httpClient.SendAsync(request); HttpResponseMessage response = await _httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode) if (!response.IsSuccessStatusCode)
{ {
return false; return false;
} }
return true;
var responsecontent = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<IntrospectionResponse>(responsecontent).Active;
} }
} }

View File

@@ -1,7 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy; using Microsoft.AspNetCore.HttpsPolicy;
@@ -12,6 +16,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using WebAPI.Auth;
using WebAPI.Data; using WebAPI.Data;
namespace WebAPI namespace WebAPI
@@ -61,8 +66,21 @@ namespace WebAPI
}); });
}); });
services.AddDbContext<AppDbContext>(options => options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection"))); services.AddDbContext<AppDbContext>(options => options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection")));
services.AddSingleton<PterodactylService>(); services.AddScoped<PterodactylService>();
services.AddSingleton<OIDCService>(); services.AddScoped<OIDCService>();
services.AddScoped<CustomAuthorizationFilter>();
services.AddAuthentication(opt =>
{
opt.DefaultScheme = OIDCTokenAuthenticationDefaults.DefaultScheme;
})
.AddScheme<OIDCTokenAuthenticationOptions, OIDCTokenAuthenticationHandler>(
OIDCTokenAuthenticationDefaults.DefaultScheme,
opt =>
{
opt.OIDCClientId = AppSettings.OIDCClientId;
opt.OIDCClientSecret = AppSettings.OIDCClientSecret;
opt.OIDCIntrospectionEndpoint = AppSettings.OIDCIntrospectionEndpoint;
});
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@@ -79,6 +97,7 @@ namespace WebAPI
app.UseRouting(); app.UseRouting();
app.UseAuthentication();
app.UseAuthorization(); app.UseAuthorization();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); app.UseEndpoints(endpoints => { endpoints.MapControllers(); });

View File

@@ -4,8 +4,8 @@
}, },
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Information", "Default": "Debug",
"Microsoft": "Warning", "Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Information" "Microsoft.Hosting.Lifetime": "Information"
} }
} }

View File

@@ -12,5 +12,7 @@
"AllowedHosts": "*", "AllowedHosts": "*",
"PterodactylAPIKey": "REPLACE_ME", "PterodactylAPIKey": "REPLACE_ME",
"PterodactylPanelURL": "https://panel.orfl.xyz", "PterodactylPanelURL": "https://panel.orfl.xyz",
"OIDCUserInfoEndpoint": "https://authentik.mattstop.com/application/o/userinfo/" "OIDCIntrospectionEndpoint": "authentik.mattstop.com/application/o/introspect/",
"OIDCClientId": "",
"OIDCClientSecret": ""
} }