using System.ComponentModel.DataAnnotations; using System.Text.Json; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Mvc; using OAuthServer.Services; namespace OAuthServer.Controllers; [ApiController] [Route("oauth")] public class OAuthController( ILogger logger, JwtService jwt, IDataProtectionProvider dataProtectionProvider ) : ControllerBase { private readonly Dictionary _clients = new() { {"lmao", "yeet"}, }; [Authorize] [HttpGet("authorize")] // ReSharper disable InconsistentNaming public ActionResult Authorize( [Required, Url] string redirect_uri, string? response_type, string? client_id, string? state, string? nonce ) { if (string.IsNullOrEmpty(response_type) || string.IsNullOrEmpty(client_id) || string.IsNullOrEmpty(state)) { return Redirect($"{redirect_uri}?error=invalid_request"); } if (response_type != "code") { return Redirect($"{redirect_uri}?error=unsupported_response_type&state={state}"); } if (!_clients.ContainsKey(client_id)) { logger.LogInformation("Unknown client id"); return Redirect($"{redirect_uri}?error=unauthorized_client&state={state}"); } IDataProtector protector = dataProtectionProvider.CreateProtector("oauth"); CodeObject codeObject = new CodeObject( ClientId: client_id, RedirectUri: redirect_uri, Expiry: DateTime.UtcNow.AddMinutes(5), Nonce: nonce ); string code = protector.Protect(JsonSerializer.Serialize(codeObject)); return Redirect($"{redirect_uri}?code={code}&state={state}"); } public record GenerateTokenRequest( string? grant_type, string? code, string? redirect_uri, string? client_id, string? client_secret ); [HttpPost("token")] [Consumes("application/x-www-form-urlencoded")] public ActionResult GenerateToken([FromForm] GenerateTokenRequest request) { if (string.IsNullOrEmpty(request.grant_type) || string.IsNullOrEmpty(request.code) || string.IsNullOrEmpty(request.redirect_uri) || string.IsNullOrEmpty(request.client_id) || string.IsNullOrEmpty(request.client_secret)) { return BadRequest(new {error = "invalid_request"}); } if (request.grant_type != "authorization_code") { return BadRequest(new {error = "unsupported_grant_type"}); } if (!_clients.TryGetValue(request.client_id, out string? clientSecret)) { logger.LogInformation("Unknown client id"); return BadRequest(new {error = "unauthorized_client"}); } if (request.client_secret != clientSecret) { logger.LogInformation("Invalid client secret"); return BadRequest(new {error = "unauthorized_client"}); } IDataProtector protector = dataProtectionProvider.CreateProtector("oauth"); CodeObject? codeObject; try { codeObject = JsonSerializer.Deserialize(protector.Unprotect(request.code)); } catch (Exception) { return BadRequest(new {error = "invalid_request"}); } if (codeObject == null) { return BadRequest(new {error = "invalid_request"}); } if (codeObject.ClientId != request.client_id || codeObject.RedirectUri != request.redirect_uri) { return BadRequest(new {error = "invalid_request"}); } if (DateTime.UtcNow > codeObject.Expiry) { logger.LogInformation("Expired token"); return BadRequest(new {error = "invalid_grant"}); } string token = jwt.GenerateToken(codeObject.ClientId, codeObject.Nonce); Response.Headers.Append("Cache-Control", "no-store"); Response.Headers.Append("Pragma", "no-cache"); return Ok(new {access_token = token, token_type = "bearer", id_token = token}); } private record CodeObject(string ClientId, string RedirectUri, DateTime Expiry, string? Nonce); }