From 8a270a7486636a453e72d4bc432f3c5b4aca02a5 Mon Sep 17 00:00:00 2001 From: D4VID Date: Mon, 17 Jun 2024 17:27:56 +0200 Subject: [PATCH] More validation --- OAuthServer/Controllers/OAuthController.cs | 47 +++++++++++++++++----- OAuthServer/Services/JwtService.cs | 9 +++-- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/OAuthServer/Controllers/OAuthController.cs b/OAuthServer/Controllers/OAuthController.cs index c2700d3..bc0dab8 100644 --- a/OAuthServer/Controllers/OAuthController.cs +++ b/OAuthServer/Controllers/OAuthController.cs @@ -14,8 +14,12 @@ public class OAuthController( JwtService jwt, IDataProtectionProvider dataProtectionProvider ) : ControllerBase { - private readonly Dictionary _clients = new() { - {"lmao", "yeet"}, + private record Client(string ClientId, string ClientSecret, string ClientOrigin, string ClientScope); + + private record CodeObject(string ClientId, string RedirectUri, DateTime Expiry); + + private readonly Dictionary _clients = new() { + {"lmao", new Client("lmao", "yeet", "http://localhost:5126", "41")}, }; @@ -28,26 +32,35 @@ public class OAuthController( string? client_id, string? state ) { + // Check if the required fields are present if (string.IsNullOrEmpty(response_type) || string.IsNullOrEmpty(client_id) || string.IsNullOrEmpty(state)) { return Redirect($"{redirect_uri}?error=invalid_request"); } + // The only supported option is "code" if (response_type != "code") { return Redirect($"{redirect_uri}?error=unsupported_response_type&state={state}"); } - if (!_clients.ContainsKey(client_id)) { + // Check if the client exists + if (!_clients.TryGetValue(client_id, out Client? client)) { logger.LogInformation("Unknown client id"); return Redirect($"{redirect_uri}?error=unauthorized_client&state={state}"); } + // Check if the origin matches the pre-registered one + string origin = GetOrigin(redirect_uri); + if (origin != client.ClientOrigin) { + return Redirect($"{redirect_uri}?error=unauthorized_client&state={state}"); + } + + // Turn the client info with an expiration to a opaque value using the data protection api IDataProtector protector = dataProtectionProvider.CreateProtector("oauth"); CodeObject codeObject = new CodeObject( ClientId: client_id, RedirectUri: redirect_uri, Expiry: DateTime.UtcNow.AddMinutes(5) ); - string code = protector.Protect(JsonSerializer.Serialize(codeObject)); return Redirect($"{redirect_uri}?code={code}&state={state}"); @@ -64,6 +77,7 @@ public class OAuthController( [HttpPost("token")] [Consumes("application/x-www-form-urlencoded")] public ActionResult GenerateToken([FromForm] GenerateTokenRequest request) { + // Check if all the required fields are present if (string.IsNullOrEmpty(request.grant_type) || string.IsNullOrEmpty(request.code) || string.IsNullOrEmpty(request.redirect_uri) || @@ -72,20 +86,24 @@ public class OAuthController( return BadRequest(new {error = "invalid_request"}); } + // The only supported option is "authorization_code" if (request.grant_type != "authorization_code") { return BadRequest(new {error = "unsupported_grant_type"}); } - if (!_clients.TryGetValue(request.client_id, out string? clientSecret)) { + // Check if the client exists + if (!_clients.TryGetValue(request.client_id, out Client? client)) { logger.LogInformation("Unknown client id"); return BadRequest(new {error = "unauthorized_client"}); } - if (request.client_secret != clientSecret) { + // Check if the client secret matches + if (request.client_secret != client.ClientSecret) { logger.LogInformation("Invalid client secret"); return BadRequest(new {error = "unauthorized_client"}); } + // Retrieve client information set by the preceding authorization step IDataProtector protector = dataProtectionProvider.CreateProtector("oauth"); CodeObject? codeObject; try { @@ -98,22 +116,33 @@ public class OAuthController( return BadRequest(new {error = "invalid_request"}); } + // Check if the values are consistent if (codeObject.ClientId != request.client_id || codeObject.RedirectUri != request.redirect_uri) { return BadRequest(new {error = "invalid_request"}); } + // Check the token's expiration if (DateTime.UtcNow > codeObject.Expiry) { logger.LogInformation("Expired token"); return BadRequest(new {error = "invalid_grant"}); } - string token = jwt.GenerateToken(); + // Generate the auth token for the application server + string token = jwt.GenerateToken("1", client.ClientId, "External", client.ClientScope); + // Add http headers to prevent caching Response.Headers.Append("Cache-Control", "no-store"); Response.Headers.Append("Pragma", "no-cache"); - return Ok(new {access_token = token, token_type = "bearer"}); + return Ok(new { + access_token = token, + token_type = "bearer", + scope = client.ClientScope + }); } - private record CodeObject(string ClientId, string RedirectUri, DateTime Expiry); + private static string GetOrigin(string url) { + Uri uri = new Uri(url); + return $"{uri.Scheme}://{uri.Authority}"; + } } \ No newline at end of file diff --git a/OAuthServer/Services/JwtService.cs b/OAuthServer/Services/JwtService.cs index 1c00955..fcce7d7 100644 --- a/OAuthServer/Services/JwtService.cs +++ b/OAuthServer/Services/JwtService.cs @@ -31,14 +31,15 @@ public class JwtService { return rsaKey; } - public string GenerateToken() { + public string GenerateToken(string userId, string clientId, string role, string scope) { var handler = new JsonWebTokenHandler(); var key = new RsaSecurityKey(_rsaKey); var token = handler.CreateToken(new SecurityTokenDescriptor { Subject = new ClaimsIdentity(new[] { - new Claim(JwtRegisteredClaimNames.Sub, "user1"), - new Claim("role", "External"), - new Claim("scope", "scope:1") + new Claim(JwtRegisteredClaimNames.Sub, userId), + new Claim("client", clientId), + new Claim("role", role), + new Claim("scope", scope) }), Expires = DateTime.UtcNow.AddDays(10), SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.RsaSha256)