Keycloak Gold - bcgov/PIMS GitHub Wiki

Background

The Pathfinder-SSO team announced that as of February 2023, their Keycloak Silver service would be coming to an end, and in it's place the new Keycloak Gold. The Keycloak Silver service allowed users to request a custom Keycloak Realm for use in their application, which provided a huge amount of customization capabilities, whereas Keycloak Gold was a shared realm for all apps, and only contains the features provided by the Pathfinder-SSO team.

What's Changed

Agencies

Each user's agency(ies) was previously found within the PIMS Keycloak Silver realm. Due to the changeover and removal of features from Keycloak, we now can only retrieve each user's roles from our database, which changed the code for getting a user's agencies in the backend from:

// OLD
var userAgencies = this.User.GetAgencies();

to:

// NEW
var user = this.Context.Users
                .Include(u => u.Agencies)
                .ThenInclude(a => a.Agency)
                .ThenInclude(a => a.Children)
                .SingleOrDefault(u => u.Username == this.User.GetUsername()) ?? throw new KeyNotFoundException();
var userAgencies = user.Agencies.Select(a => a.AgencyId).ToList();

Get Agencies Endpoint

Due to the users agencies no longer being stored within the access token, upon page load, the frontend now makes a request to the /users/agencies endpoint to fetch the user's agencies upon page load. This should be a temporary fix however, and this functionality should be moved to the /activate endpoint to reduce the total number of API calls made.

//TODO: "Modify the /activate endpoint to also return the users agencies, removing the need for this endpoint."
[HttpGet("agencies/{username}")]
[Produces("application/json")]
[ProducesResponseType(typeof(Model.AccessRequestModel), 200)]
[ProducesResponseType(typeof(Models.ErrorResponseModel), 400)]
[ProducesResponseType(typeof(Models.ErrorResponseModel), 403)]
[SwaggerOperation(Tags = new[] { "user" })]
public IActionResult GetUserAgencies(string username)
{
    IEnumerable<int> userAgencies = _pimsService.User.GetAgencies(username);
    return new JsonResult(userAgencies);
}

Roles

Keycloak Gold allows the granting and removal of roles on users, as well as the granting/removal of groups of roles called "Composite Roles". PIMS uses Composite Roles in place of the previous Keycloak Silver "Groups", and can be identified in the list of roles in the Keycloak Gold console as they are structured with capital letters and spaces, as opposed to the structure of regular roles being lowercase and in kebab-case.

Composite Role: System Administrator

Role: property-view

Updating Roles in Admin Page

When managing users from within the admin page, accessing said user's composite roles requires an extra call to the Keycloak Gold API, using PIMS' service account credentials. With said roles, we allow the frontend to add/remove composite roles from specific users. Each addition/removal of a role to a user now has it's own api call, and the edit user page's UI is updated to reflect that.

The user role selection is now below the save button, and is separated by a divider, implying that the role management is separated from the standard updating user feature.

Updating Roles Endpoints

/// <summary>
/// POST - Add a role to the user by calling the Keycloak Gold API.
/// </summary>
/// <param name="username">The user's username</param>
/// <param name="role">A JSON object with the name of the role to add.</param>
/// <returns>JSON Array of the users roles, updated with the one just added.</returns>
[HttpPost("role/{username}")]
[Produces("application/json")]
[ProducesResponseType(typeof(Model.UserModel), 200)]
[ProducesResponseType(typeof(Api.Models.ErrorResponseModel), 400)]
[SwaggerOperation(Tags = new[] { "admin-user" })]
public IActionResult AddRoleToUser(string username, [FromBody] Dictionary<string, string> role)
{
        var user = _pimsAdminService.User.Get(username);
        var preferred_username = _pimsAdminService.User.GetUsersPreferredUsername(user.KeycloakUserId ?? Guid.Empty, user.Username.Split("@").Last()).Result;
        var res = _pimsAdminService.User.AddRoleToUser(preferred_username, role.GetValueOrDefault("name")).Result;
        return new JsonResult(res);

}

/// <summary>
/// DELETE - Remove a role from the user by calling the Keycloak Gold API.
/// </summary>
/// <param name="username">The user's username</param>
/// <param name="role">A JSON object with the name of the role to remove.</param>
/// <returns>JSON Array of the users roles, updated with the one just added.</returns>
[HttpDelete("role/{username}")]
[Produces("application/json")]
[ProducesResponseType(typeof(Model.UserModel), 200)]
[ProducesResponseType(typeof(Api.Models.ErrorResponseModel), 400)]
[SwaggerOperation(Tags = new[] { "admin-user" })]
public IActionResult DeleteRoleFromUser(string username, [FromBody] Dictionary<string, string> role)
{
        var user = _pimsAdminService.User.Get(username);
        var preferred_username = _pimsAdminService.User.GetUsersPreferredUsername(user.KeycloakUserId ?? Guid.Empty, user.Username.Split("@").Last()).Result;
        var res = _pimsAdminService.User.DeleteRoleFromUser(preferred_username, role.GetValueOrDefault("name")).Result;
        return new JsonResult(res);
}

Keycloak Dev Instance

Keycloak Gold only provides 3 environments per "integration", dev, test, and prod. Due to the PIMS team needing a fourth environment for local, we now manage 2 keycloak integrations, PIMS-Frontend-Local, and PIMS-Frontend. This results in the removal of the keycloak docker container within our local development environment, bringing the total container count for local down to 3.

Keycloak Workflow

The Keycloak Gold workflow is as follows

Accessing Requesting User's Info in the API

For every API request, the controller has access to the requesting user's decoded JWT token in the form of this.User. Said reference to the user contains extension methods which allow for easy consumption of the user, such as this.User.GetUsername() or this.User.GetEmail(). These extension methods, found in IdentityExtensions.cs, have been updated to align with the claims on the new Keycloak Gold access tokens.

⚠️ **GitHub.com Fallback** ⚠️