diff --git a/Web/Pages/CreateServer.razor b/Web/Pages/CreateServer.razor index 1a3029e..a7c81a3 100644 --- a/Web/Pages/CreateServer.razor +++ b/Web/Pages/CreateServer.razor @@ -7,7 +7,7 @@ @code { - protected async override Task OnInitializedAsync() + protected override async Task OnInitializedAsync() { base.OnInitializedAsync(); } diff --git a/WebAPI/Auth/CustomAuthorizationFilter.cs b/WebAPI/Auth/CustomAuthorizationFilter.cs new file mode 100644 index 0000000..698e510 --- /dev/null +++ b/WebAPI/Auth/CustomAuthorizationFilter.cs @@ -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)) + { + + } + } + /// + /// Authorization filter for checking the bearer token against the OIDC service. + /// Will short circuit on an invalid token + /// + /// + public class CustomAuthorizationFilter: IAsyncAuthorizationFilter + { + private readonly ILogger _logger; + private readonly OIDCService _oidcService; + private readonly AppDbContext _appDbContext; + + public CustomAuthorizationFilter(ILogger 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); + } + } + } +} \ No newline at end of file diff --git a/WebAPI/Auth/OIDCAuthHandler.cs b/WebAPI/Auth/OIDCAuthHandler.cs new file mode 100644 index 0000000..53021aa --- /dev/null +++ b/WebAPI/Auth/OIDCAuthHandler.cs @@ -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 + { + private readonly OIDCService _oidcService; + public OIDCTokenAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, OIDCService oidcService) : base(options, logger, encoder, clock) + { + _oidcService = oidcService; + } + + protected override async Task 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"; + } +} \ No newline at end of file diff --git a/WebAPI/Controllers/HelloWorldController.cs b/WebAPI/Controllers/HelloWorldController.cs index e0b434c..ea37667 100644 --- a/WebAPI/Controllers/HelloWorldController.cs +++ b/WebAPI/Controllers/HelloWorldController.cs @@ -2,33 +2,30 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using WebAPI.Auth; using WebAPI.Data; namespace WebAPI.Controllers { + [Authorize(Policy = "test")] [Route("api/[controller]")] [ApiController] public class HelloWorldController : BaseController { private readonly PterodactylService _pterodactylService; - private readonly OIDCService _oidcService; - public HelloWorldController(PterodactylService pterodactylService, OIDCService oidcService) + public HelloWorldController(PterodactylService pterodactylService) { _pterodactylService = pterodactylService; - _oidcService = oidcService; } [HttpGet] public async Task HelloWorld() { - if (await _oidcService.ValidateAccessToken(BearerToken)) - { - return "Validated"; - } - return "Failed"; + return "Success"; } } diff --git a/WebAPI/Data/AppSettings.cs b/WebAPI/Data/AppSettings.cs index 4711cf1..f8ef49e 100644 --- a/WebAPI/Data/AppSettings.cs +++ b/WebAPI/Data/AppSettings.cs @@ -7,7 +7,9 @@ namespace WebAPI.Data { public static string PterodactylAPIKey { 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) { var fields = typeof(AppSettings).GetProperties(); diff --git a/WebAPI/Data/OIDCService.cs b/WebAPI/Data/OIDCService.cs index 2ed297f..de8a665 100644 --- a/WebAPI/Data/OIDCService.cs +++ b/WebAPI/Data/OIDCService.cs @@ -1,14 +1,21 @@ using System; +using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Headers; +using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; +using Newtonsoft.Json; namespace WebAPI.Data { public class OIDCService { + protected class IntrospectionResponse + { + public bool Active { get; set; } + } private HttpClient _httpClient { get; set; } private ILogger _logger { get; set; } public OIDCService(ILogger logger) @@ -24,15 +31,23 @@ namespace WebAPI.Data /// success public async Task ValidateAccessToken(string accessToken) { - Uri requestUri = new Uri($"{AppSettings.OIDCUserInfoEndpoint}"); - HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUri); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + Uri requestUri = new Uri($"https://{AppSettings.OIDCIntrospectionEndpoint}"); + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, requestUri); + request.Content = new FormUrlEncodedContent(new Dictionary() + { + {"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); if (!response.IsSuccessStatusCode) { return false; } - return true; + + var responsecontent = await response.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject(responsecontent).Active; } } diff --git a/WebAPI/Startup.cs b/WebAPI/Startup.cs index 28bf8ed..37c54ac 100644 --- a/WebAPI/Startup.cs +++ b/WebAPI/Startup.cs @@ -1,7 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Security.Claims; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.HttpsPolicy; @@ -12,6 +16,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Models; +using WebAPI.Auth; using WebAPI.Data; namespace WebAPI @@ -61,8 +66,21 @@ namespace WebAPI }); }); services.AddDbContext(options => options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection"))); - services.AddSingleton(); - services.AddSingleton(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddAuthentication(opt => + { + opt.DefaultScheme = OIDCTokenAuthenticationDefaults.DefaultScheme; + }) + .AddScheme( + 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. @@ -79,6 +97,7 @@ namespace WebAPI app.UseRouting(); + app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); diff --git a/WebAPI/appsettings.Development.json b/WebAPI/appsettings.Development.json index 2900c0a..182c7fb 100644 --- a/WebAPI/appsettings.Development.json +++ b/WebAPI/appsettings.Development.json @@ -4,8 +4,8 @@ }, "Logging": { "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", + "Default": "Debug", + "Microsoft": "Debug", "Microsoft.Hosting.Lifetime": "Information" } } diff --git a/WebAPI/appsettings.json b/WebAPI/appsettings.json index 01fe3bc..36832eb 100644 --- a/WebAPI/appsettings.json +++ b/WebAPI/appsettings.json @@ -12,5 +12,7 @@ "AllowedHosts": "*", "PterodactylAPIKey": "REPLACE_ME", "PterodactylPanelURL": "https://panel.orfl.xyz", - "OIDCUserInfoEndpoint": "https://authentik.mattstop.com/application/o/userinfo/" + "OIDCIntrospectionEndpoint": "authentik.mattstop.com/application/o/introspect/", + "OIDCClientId": "", + "OIDCClientSecret": "" }