using IdentityShroud.Api.Apis; using IdentityShroud.Api.Apis.ISResults; using IdentityShroud.Api.Helpers; using IdentityShroud.Api.Mappers; using IdentityShroud.Core.Contracts; using IdentityShroud.Core.Messages; using IdentityShroud.Core.Model; using IdentityShroud.Core.Services.OpenId; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; namespace IdentityShroud.Api; public static class OpenIdEndpoints { // openid: auth/realms/{realmSlug}/.well-known/openid-configuration // openid: auth/realms/{realmSlug}/openid-connect/(auth|token|jwks) public static void MapEndpoints(this IEndpointRouteBuilder erp) { var realmsGroup = erp.MapGroup("/auth/realms"); var realmSlugGroup = realmsGroup.MapGroup("{realmSlug}") .AddEndpointFilter(); realmSlugGroup.MapGet(".well-known/openid-configuration", GetOpenIdConfiguration); var openidConnect = realmSlugGroup.MapGroup("openid-connect"); openidConnect.MapPost("auth", OpenIdConnectAuth); openidConnect.MapPost("token", OpenIdConnectToken); openidConnect.MapGet("jwks", OpenIdConnectJwks); } private static async Task> GetOpenIdConfiguration( string realmSlug, [FromServices]IRealmService realmService, HttpContext context) { Realm realm = context.GetValidatedRealm(); var s = $"{context.Request.Scheme}://{context.Request.Host}{context.Request.Path}"; var searchString = $"realms/{realmSlug}"; int index = s.IndexOf(searchString, StringComparison.OrdinalIgnoreCase); string baseUri = s.Substring(0, index + searchString.Length); return TypedResults.Json(new OpenIdConfiguration() { AuthorizationEndpoint = baseUri + "/openid-connect/auth", TokenEndpoint = baseUri + "/openid-connect/token", Issuer = baseUri, JwksUri = baseUri + "/openid-connect/jwks", }, AppJsonSerializerContext.Default.OpenIdConfiguration); } private static async Task, BadRequest>> OpenIdConnectJwks( string realmSlug, [FromServices]IRealmService realmService, [FromServices]KeyMapper keyMapper, HttpContext context) { Realm realm = context.GetValidatedRealm(); await realmService.LoadActiveKeys(realm); return TypedResults.Ok(keyMapper.KeyListToJsonWebKeySet(realm.TokenSigningKeys)); } private static async Task, BadRequest, ISUnauthorizedHttpResult >> OpenIdConnectToken( string realmSlug, [FromServices] IClientService clientService, HttpContext context, CancellationToken ct) { IFormCollection form = await context.Request.ReadFormAsync(); string grantType = form["grant_type"].ToString(); string clientId = form["client_id"].ToString(); string scope = form["scope"].ToString(); if (grantType == "client_credentials") { string? clientSecret = null; bool withAuthHeader = false; if (HeaderHelpers.TryGetBasicAuth(context, out string? user, out string? password)) { withAuthHeader = true; clientId = user; clientSecret = password; } clientSecret ??= form["client_secret"].ToString(); if (string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(clientSecret)) { return CreateBadRequest("invalid_request"); } Realm realm = context.GetValidatedRealm(); Client? client = await clientService.GetByClientId(realm.Id, clientId, ct); if (client is null) { if (withAuthHeader) { return new ISUnauthorizedHttpResult([$"Basic realm=\"{realm.Slug}\""]); } return CreateBadRequest("invalid_client"); } if (!client.AllowClientCredentialsFlow) return CreateBadRequest("unauthorized_client"); } else return CreateBadRequest("unsupported_grant_type"); context.Response.Headers.CacheControl = "no-store"; context.Response.Headers.Pragma = "no-cache"; return TypedResults.Ok(new TokenResponse() { AccessToken = "token", TokenType = "bearer", ExpiresIn = 3600, }); } private static BadRequest CreateBadRequest(string error) => TypedResults.BadRequest(new ErrorDto(error)); private static Task OpenIdConnectAuth(HttpContext context) { throw new NotImplementedException(); } }