Skip to content

Commit

Permalink
FEAT: Update Keycloak config for client roles rather than realm roles (
Browse files Browse the repository at this point in the history
…#43)

## Description
Closes #42 and implements new methods for checking roles belonging to a
client.
Also performs some cleanup of configurations and upgrade to latest
version of Keycloak.

## Related Issue
Closes #42.

## Motivation and Context
Allows better and more flexible management of roles for different
resources (APIs) and allows to check for them beyond the standard `role`
claim in the tokens.

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [x] Bug fix (non-breaking change which fixes an issue)
- [X] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)

## Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes
that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're
here to help! -->
- [X] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [X] I have read the **CONTRIBUTING** document.
- [ ] I have added tests to cover my changes.
- [X] All new and existing tests passed.

---------

Co-authored-by: Gonzalo Fernández <[email protected]>
  • Loading branch information
CesarD and Gonzo345 authored Mar 7, 2023
1 parent 65dbdd9 commit d963ea5
Show file tree
Hide file tree
Showing 3 changed files with 355 additions and 240 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,12 @@

//#if (!disableAuth)
"SSO": {
"Authority": "http://localhost:8080/auth/realms/monaco-template",
"Authority": "http://localhost:8080/realms/monaco-template",
"Audience": "monaco-template-api",

"SwaggerUIClientId": "monaco-template-api-swagger-ui",
"SwaggerUIClientSecret": ""
},

"KeyCloak": {
"Host": "http://localhost:8080",
"Realm": "monaco-template",
"ClientSecret": ""
},

//#endif
//#if (massTransitIntegration)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using Microsoft.IdentityModel.JsonWebTokens;
using System.Security.Claims;
using System.Text.Json;
using System.Text.Json.Nodes;

namespace Monaco.Template.Backend.Common.Application.Extensions;

public static class ClaimsPrincipalExtensions
{
private const string ResourceAccessClaimName = "resource_access";

/// <summary>
/// Retrieves the User Id from the "sub" claim
/// </summary>
/// <param name="principal"></param>
/// <returns></returns>
public static Guid? GetUserId(this ClaimsPrincipal principal) =>
principal.HasClaim(c => c.Type == JwtRegisteredClaimNames.Sub)
? Guid.Parse(principal.FindFirst(JwtRegisteredClaimNames.Sub)!.Value)
: null;

/// <summary>
/// Determines if the user has the specified role on the specified client
/// </summary>
/// <param name="principal"></param>
/// <param name="clientName"></param>
/// <param name="roleName"></param>
/// <returns></returns>
public static bool IsInClientRole(this ClaimsPrincipal principal, string clientName, string roleName)
{
var resourceAccessClaim = principal.FindFirst(ResourceAccessClaimName);
if (resourceAccessClaim is null)
return false;

var clients = JsonSerializer.Deserialize<Dictionary<string, JsonObject>>(resourceAccessClaim.Value,
new JsonSerializerOptions(JsonSerializerDefaults.Web));
return clients is not null &&
clients.ContainsKey(clientName) &&
(clients[clientName][principal.Identities.First().RoleClaimType]?.Deserialize<string[]>()?.Contains(roleName) ?? false);
}

/// <summary>
/// Determines if the user has the specified role in the client specified by the Audience (aud) claim
/// </summary>
/// <param name="principal"></param>
/// <param name="roleName"></param>
/// <returns></returns>
public static bool IsInClientRole(this ClaimsPrincipal principal, string roleName) =>
principal.HasClaim(c => c.Type == JwtRegisteredClaimNames.Aud) &&
principal.IsInClientRole(principal.FindFirst(JwtRegisteredClaimNames.Aud)!.Value, roleName);
}
Loading

0 comments on commit d963ea5

Please sign in to comment.