From 8c044178368461b75c730f161c1d23045ad098d1 Mon Sep 17 00:00:00 2001 From: Felipe Mattioli dos Santos Date: Wed, 10 Jul 2024 14:52:29 +0200 Subject: [PATCH] FEAT: Making policies optional to be used --- .../Extensions/AuthExtensions.cs | 202 +++++++++--------- .../Services/Models/AuthSettings.cs | 14 +- 2 files changed, 111 insertions(+), 105 deletions(-) diff --git a/src/Feijuca.Keycloak.Auth.MultiTenancy/Feijuca.Keycloak.MultiTenancy/Extensions/AuthExtensions.cs b/src/Feijuca.Keycloak.Auth.MultiTenancy/Feijuca.Keycloak.MultiTenancy/Extensions/AuthExtensions.cs index 98c72a2..3885e45 100644 --- a/src/Feijuca.Keycloak.Auth.MultiTenancy/Feijuca.Keycloak.MultiTenancy/Extensions/AuthExtensions.cs +++ b/src/Feijuca.Keycloak.Auth.MultiTenancy/Feijuca.Keycloak.MultiTenancy/Extensions/AuthExtensions.cs @@ -17,122 +17,128 @@ public static IServiceCollection AddKeyCloakAuth(this IServiceCollection service var httpClient = new HttpClient(); var tokenHandler = new JwtSecurityTokenHandler(); - services - .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) - .AddKeycloakWebApi( - options => - { - options.Resource = authSettings.Resource!; - options.AuthServerUrl = authSettings.AuthServerUrl; - options.VerifyTokenAudience = true; - }, - options => - { - options.Events = new JwtBearerEvents + services.AddSingleton() + .AddSingleton(authSettings) + .AddScoped() + .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddKeycloakWebApi( + options => + { + options.Resource = authSettings.Resource!; + options.AuthServerUrl = authSettings.AuthServerUrl; + options.VerifyTokenAudience = true; + }, + options => { - OnMessageReceived = async context => + options.Events = new JwtBearerEvents { - try + OnMessageReceived = async context => { - var tokenJwt = context.Request.Headers.Authorization.FirstOrDefault(); - - if (string.IsNullOrEmpty(tokenJwt)) + try { - context.HttpContext.Items["AuthError"] = "Invalid JWT token provided! Please check. "; - context.HttpContext.Items["AuthStatusCode"] = 401; - return; - } + var tokenJwt = context.Request.Headers.Authorization.FirstOrDefault(); - var bearerToken = tokenJwt.Replace("Bearer ", ""); - var tokenInfos = tokenHandler.ReadJwtToken(tokenJwt.Replace("Bearer ", "")); - var tenantNumber = tokenInfos.Claims.FirstOrDefault(c => c.Type == "tenant")?.Value; - var tenantRealm = authSettings.Realms.FirstOrDefault(realm => realm.Name == tenantNumber); + if (string.IsNullOrEmpty(tokenJwt)) + { + context.HttpContext.Items["AuthError"] = "Invalid JWT token provided! Please check. "; + context.HttpContext.Items["AuthStatusCode"] = 401; + return; + } - if (tenantRealm is null) - { - context.HttpContext.Items["AuthError"] = "This token don't belongs to valid tenant. Please check!"; - context.HttpContext.Items["AuthStatusCode"] = 401; - context.NoResult(); - return; - } + var bearerToken = tokenJwt.Replace("Bearer ", ""); + var tokenInfos = tokenHandler.ReadJwtToken(tokenJwt.Replace("Bearer ", "")); + var tenantNumber = tokenInfos.Claims.FirstOrDefault(c => c.Type == "tenant")?.Value; + var tenantRealm = authSettings.Realms.FirstOrDefault(realm => realm.Name == tenantNumber); - var audience = tokenInfos.Claims.FirstOrDefault(c => c.Type == "aud")?.Value; - if (string.IsNullOrEmpty(audience)) - { - context.HttpContext.Items["AuthError"] = "Invalid scope provided! Please, check the scopes provided!"; - context.HttpContext.Items["AuthStatusCode"] = 403; - context.NoResult(); - return; - } + if (tenantRealm is null) + { + context.HttpContext.Items["AuthError"] = "This token don't belongs to valid tenant. Please check!"; + context.HttpContext.Items["AuthStatusCode"] = 401; + context.NoResult(); + return; + } - var jwksUrl = $"{tenantRealm.Issuer}/protocol/openid-connect/certs"; + var audience = tokenInfos.Claims.FirstOrDefault(c => c.Type == "aud")?.Value; + if (string.IsNullOrEmpty(audience)) + { + context.HttpContext.Items["AuthError"] = "Invalid scope provided! Please, check the scopes provided!"; + context.HttpContext.Items["AuthStatusCode"] = 403; + context.NoResult(); + return; + } - var jwks = await httpClient.GetStringAsync(jwksUrl); - var jsonWebKeySet = new JsonWebKeySet(jwks); + var jwksUrl = $"{tenantRealm.Issuer}/protocol/openid-connect/certs"; - var tokenValidationParameters = new TokenValidationParameters - { - ValidateIssuer = true, - ValidIssuer = tenantRealm.Issuer, - ValidateAudience = true, - ValidAudience = tenantRealm.Audience, - ValidateLifetime = true, - ValidateIssuerSigningKey = true, - IssuerSigningKeys = jsonWebKeySet.Keys - }; + var jwks = await httpClient.GetStringAsync(jwksUrl); + var jsonWebKeySet = new JsonWebKeySet(jwks); - var claims = tokenHandler.ValidateToken(bearerToken, tokenValidationParameters, out var validatedToken); - context.Principal = claims; - context.Success(); - } - catch (Exception e) - { - context.Response.StatusCode = 500; - context.HttpContext.Items["AuthError"] = "The following error occurs during the authentication process: " + e.Message; - context.Fail(""); - } - }, - OnAuthenticationFailed = async context => - { - var errorDescription = context.Exception.Message; - context.Response.StatusCode = 401; - context.Response.ContentType = "application/json"; - await context.Response.WriteAsJsonAsync(errorDescription); - }, - OnChallenge = async context => - { - if (!context.Response.HasStarted) + var tokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidIssuer = tenantRealm.Issuer, + ValidateAudience = true, + ValidAudience = tenantRealm.Audience, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + IssuerSigningKeys = jsonWebKeySet.Keys + }; + + var claims = tokenHandler.ValidateToken(bearerToken, tokenValidationParameters, out var validatedToken); + context.Principal = claims; + context.Success(); + } + catch (Exception e) + { + context.Response.StatusCode = 500; + context.HttpContext.Items["AuthError"] = "The following error occurs during the authentication process: " + e.Message; + context.Fail(""); + } + }, + OnAuthenticationFailed = async context => { - var errorMessage = context.HttpContext.Items["AuthError"] as string ?? "Authentication failed!"; - var statusCode = context.HttpContext.Items["AuthStatusCode"] as int? ?? 401; - var responseMessage = new { Message = errorMessage }; - context.Response.StatusCode = statusCode; + var errorDescription = context.Exception.Message; + context.Response.StatusCode = 401; context.Response.ContentType = "application/json"; - await context.Response.WriteAsJsonAsync(responseMessage); + await context.Response.WriteAsJsonAsync(errorDescription); + }, + OnChallenge = async context => + { + if (!context.Response.HasStarted) + { + var errorMessage = context.HttpContext.Items["AuthError"] as string ?? "Authentication failed!"; + var statusCode = context.HttpContext.Items["AuthStatusCode"] as int? ?? 401; + var responseMessage = new { Message = errorMessage }; + context.Response.StatusCode = statusCode; + context.Response.ContentType = "application/json"; + await context.Response.WriteAsJsonAsync(responseMessage); + } + context.HandleResponse(); } - context.HandleResponse(); - } - }; - } - ); + }; + } + ); services .AddAuthorization() - .AddKeycloakAuthorization() - .AddAuthorizationBuilder() - .AddPolicy( - authSettings.PolicyName!, - policy => - { - policy.RequireResourceRolesForClient( - authSettings!.Resource!, - authSettings!.Roles!.ToArray()); - } - ); + .AddKeycloakAuthorization(); - services.AddSingleton() - .AddSingleton(authSettings) - .AddScoped(); + if (!string.IsNullOrEmpty(authSettings?.PolicyName)) + { + services.AddAuthorizationBuilder() + .AddPolicy( + authSettings.PolicyName, + policy => + { + policy.RequireResourceRolesForClient( + authSettings.Resource!, + authSettings.Roles!.ToArray()); + } + ); + } + else + { + services.AddAuthorizationBuilder(); + } return services; } diff --git a/src/Feijuca.Keycloak.Auth.MultiTenancy/Feijuca.Keycloak.MultiTenancy/Services/Models/AuthSettings.cs b/src/Feijuca.Keycloak.Auth.MultiTenancy/Feijuca.Keycloak.MultiTenancy/Services/Models/AuthSettings.cs index 1251bf0..f310382 100644 --- a/src/Feijuca.Keycloak.Auth.MultiTenancy/Feijuca.Keycloak.MultiTenancy/Services/Models/AuthSettings.cs +++ b/src/Feijuca.Keycloak.Auth.MultiTenancy/Feijuca.Keycloak.MultiTenancy/Services/Models/AuthSettings.cs @@ -2,14 +2,14 @@ { public class AuthSettings { - public string? ClientId { get; set; } - public string? ClientSecret { get; set; } - public string? Resource { get; set; } - public string? AuthServerUrl { get; set; } + public required string ClientId { get; set; } + public required string ClientSecret { get; set; } + public required string Resource { get; set; } + public required string AuthServerUrl { get; set; } + public required IEnumerable Realms { get; set; } public string? PolicyName { get; set; } - public IEnumerable Roles { get; set; } = []; - public IEnumerable Scopes { get; set; } = []; - public IEnumerable Realms { get; set; } = []; + public IEnumerable? Roles { get; set; } = []; + public IEnumerable? Scopes { get; set; } = []; } public class Realm