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 }); } [Authorize] [HttpGet("user")] public ActionResult UserInfo() { return Ok(new { sub = "248289761001", name = "Jane Doe", // given_name = "Jane", // family_name = "Doe", // preferred_username = "j.doe", email = "janedoe@example.com", }); } private record CodeObject(string ClientId, string RedirectUri, DateTime Expiry, string? Nonce); }