Added userinfo endpoint usage and api now properly creates necessary claims to start doing database stuff

This commit is contained in:
2021-10-18 11:06:44 -04:00
parent 320d939c76
commit 9e6c7f33a5
13 changed files with 129 additions and 88 deletions

View File

@@ -36,22 +36,21 @@ namespace Web
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(options =>
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme,options =>
{
options.Authority = Configuration["oidc:authority"];
options.ClientId = Configuration["oidc:client_id"];
options.ClientSecret = Configuration["oidc:client_secret"];
options.ResponseType = OpenIdConnectResponseType.Code;
options.GetClaimsFromUserInfoEndpoint = true;
options.GetClaimsFromUserInfoEndpoint = false;
options.SaveTokens = true;
options.UseTokenLifetime = true;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add(OpenIdConnectScope.OpenIdProfile);
options.Scope.Add(OpenIdConnectScope.OpenId);
options.TokenValidationParameters = new
TokenValidationParameters
{

View File

@@ -1,64 +0,0 @@
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

@@ -34,9 +34,17 @@ namespace WebAPI.Auth
return AuthenticateResult.Fail("failed to validate token");
}
var userInfo = await _oidcService.GetTokenDetails(token);
if (userInfo == null)
{
return AuthenticateResult.Fail("Failed to get info for token");
}
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Authentication, token)
new Claim(ClaimTypes.Authentication, token),
new Claim(ClaimTypes.Name, userInfo.Name),
new Claim(OIDCClaimTypes.Username, userInfo.PreferredUsername),
new Claim(OIDCClaimTypes.Subject, userInfo.Sub)
}, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
return AuthenticateResult.Success(new AuthenticationTicket(principal, Scheme.Name));
@@ -45,9 +53,6 @@ namespace WebAPI.Auth
public class OIDCTokenAuthenticationOptions : AuthenticationSchemeOptions
{
public string OIDCIntrospectionEndpoint { get; set; }
public string OIDCClientId { get; set; }
public string OIDCClientSecret { get; set; }
}
@@ -55,4 +60,10 @@ namespace WebAPI.Auth
{
public static string DefaultScheme => "OIDCAuthentication";
}
public static class OIDCClaimTypes
{
public static string Username => "Username";
public static string Subject => "Subject";
}
}

View File

@@ -1,5 +1,7 @@
using System;
using System.Linq;
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;
@@ -7,10 +9,5 @@ namespace WebAPI.Controllers
{
public class BaseController : ControllerBase
{
protected string BearerToken =>
Request.Headers.Keys.Contains(HeaderNames.Authorization) &&
Request.Headers[HeaderNames.Authorization].Count > 0
? Request.Headers[HeaderNames.Authorization].First().Split(" ")[1]
: String.Empty;
}
}

View File

@@ -5,12 +5,13 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using WebAPI.Auth;
using WebAPI.Data;
namespace WebAPI.Controllers
{
[Authorize(Policy = "test")]
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class HelloWorldController : BaseController
@@ -25,7 +26,14 @@ namespace WebAPI.Controllers
[HttpGet]
public async Task<string> HelloWorld()
{
return "Success";
return JsonConvert.SerializeObject(User.Claims.Select(claim => new {claim.Type, claim.Value}));
}
[HttpGet]
[Route("NameRequired")]
public string NameRequired()
{
return "success";
}
}

View File

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

View File

@@ -0,0 +1,34 @@
using Newtonsoft.Json;
namespace WebAPI.Data.Dto.OIDC
{
public class OIDCUserInfoResponse
{
[JsonProperty("email")]
public string Email { get; set; }
[JsonProperty("email_verified")]
public bool EmailVerified { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("given_name")]
public string GivenName { get; set; }
[JsonProperty("family_name")]
public string FamilyName { get; set; }
[JsonProperty("preferred_username")]
public string PreferredUsername { get; set; }
[JsonProperty("nickname")]
public string Nickname { get; set; }
[JsonProperty("groups")]
public string[] Groups { get; set; }
[JsonProperty("sub")]
public string Sub { get; set; }
}
}

View File

@@ -0,0 +1,16 @@
using Newtonsoft.Json;
namespace WebAPI.Data.Dto
{
public class PterodactylCreateUserRequest
{
public string Email { get; set; }
public string Username { get; set; }
[JsonProperty("external_id")]
public string ExternalId { get; set; }
[JsonProperty("first_name")]
public string FirstName { get; set; }
[JsonProperty("last_name")]
public string LastName { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
namespace WebAPI.Data.Dto
{
public class PterodactylCreateUserResponseAttributes
{
public int Id { get; set; }
}
public class PterodactylCreateUserResponse
{
public PterodactylCreateUserResponseAttributes Attributes { get; set; }
}
}

View File

@@ -7,6 +7,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using WebAPI.Data.Dto.OIDC;
namespace WebAPI.Data
{
@@ -50,5 +51,20 @@ namespace WebAPI.Data
return JsonConvert.DeserializeObject<IntrospectionResponse>(responsecontent).Active;
}
public async Task<OIDCUserInfoResponse> GetTokenDetails(string accessToken)
{
HttpRequestMessage requestMessage =
new HttpRequestMessage(HttpMethod.Get, $"https://{AppSettings.OIDCUserInfoEndpoint}");
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
HttpResponseMessage response = await _httpClient.SendAsync(requestMessage);
if (!response.IsSuccessStatusCode)
{
return null;
}
var responsecontent = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<OIDCUserInfoResponse>(responsecontent);
}
}
}

View File

@@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using WebAPI.Data.Dto;
namespace WebAPI.Data
{
@@ -46,7 +47,7 @@ namespace WebAPI.Data
return JsonConvert.DeserializeObject<T>(responsedata);
}
public async Task<T> SendGet<T>(string endpoint, IEnumerable includeParameters, bool client=false)
private async Task<T> SendGet<T>(string endpoint, IEnumerable includeParameters, bool client=false)
{
try
{
@@ -61,7 +62,7 @@ namespace WebAPI.Data
}
public async Task<T> SendPost<T>(string endpoint, object obj, IEnumerable includeParameters, bool client = false)
private async Task<T> SendPost<T>(string endpoint, object obj, IEnumerable includeParameters, bool client = false)
{
try
{
@@ -75,5 +76,19 @@ namespace WebAPI.Data
return default;
}
}
public async Task<PterodactylCreateUserResponse> SendPterodactylUserCreate(string username, string email,
string firstname, string lastname, string externalId)
{
var requestObj = new PterodactylCreateUserRequest()
{
Email = email,
ExternalId = externalId,
FirstName = firstname,
LastName = lastname,
Username = username
};
return await SendPost<PterodactylCreateUserResponse>("users", requestObj, null);
}
}
}

View File

@@ -68,7 +68,6 @@ namespace WebAPI
services.AddDbContext<AppDbContext>(options => options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection")));
services.AddScoped<PterodactylService>();
services.AddScoped<OIDCService>();
services.AddScoped<CustomAuthorizationFilter>();
services.AddAuthentication(opt =>
{
opt.DefaultScheme = OIDCTokenAuthenticationDefaults.DefaultScheme;
@@ -77,9 +76,6 @@ namespace WebAPI
OIDCTokenAuthenticationDefaults.DefaultScheme,
opt =>
{
opt.OIDCClientId = AppSettings.OIDCClientId;
opt.OIDCClientSecret = AppSettings.OIDCClientSecret;
opt.OIDCIntrospectionEndpoint = AppSettings.OIDCIntrospectionEndpoint;
});
}

View File

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