p2-inc / keycloak-orgs

Single realm, multi-tenancy for SaaS apps
https://phasetwo.io
Other
405 stars 71 forks source link

Active Organization SPI #149

Closed millwheel closed 7 months ago

millwheel commented 9 months ago

active-organization-spi.zip

Hello. I am a backend engineer from Logblack inc. I am using the PhaseTwo organization of keycloak extension with happiness because it is possible to manage multi-tenancy in one realm by using it.

However, I thought some functions are still needed because I had to provide the information to the users according to their active organization (activated at the point of login) because there would be a scenario that some users may have multi organizations.

I created a few SPI that provides an advanced feature about "active organization". It includes these function:

  1. Organization Selection when user Login
  2. Organization number check condition
  3. Protocol mapper to inject an "active organization" claim into token

Organization Selection

The main function is providing the option for user to select the organization when they login. The resource server may provides the resources according to the active organization the user selected.

Below is sample image of selection UI of active organization.

image

Organization Number Check Condition

There is no need to provide the organization selection login page if the user has only one organization. if the user belongs to only one organization, no organization selection page will show in the login flow. You can achieve this by constructing the authentication flow properly. (You can see the example below.)

Active Organization Protocol Mapper

This feature not only registers organizational information to keycloak, but also it can inject the "active organization" information into access token.

You can achieve this by adding mapper to your client scope that is used by login client (such as SPA, BFF or any server related to login). You can see the example of token injection setting of active organization

image

The users can choose the organization they want to activate to use resource server and the resource server will provide the information selectively depending on user's organizational rights.

Example of authentication Flow

active-organization-spi.zip

Below is the example format of the use of these SPIs. image FYI, if you follow this example, when the user doesn't have any organization, the login fail page with message of "You do not belong to any organization. Please contact the administrator." will occur.

Well.. I didn't make the PR because I don't know where to put these SPI. Just let me know where to put it in the project directory if you are interested in these functions.

I attached the ZIP file of SPIs. Anyone can download this file. Just let me know if you find the error.

MGLL commented 9 months ago

Hello millwheel,

I'm happy to see that because "active organization" was part of my questions here. On my side I had in mind to keep track of the 'active_organization' on user attributes and map this attribute in the token.

How is the active organization kept on user? Is it stored in user's attribute or only for the session? (meaning that if a user wants to switch organization, he will need to log in again)

xgp commented 9 months ago

If someone wants to adapt this to a PR, we'll look at including it.

Couple of notes after looking over this:

millwheel commented 9 months ago

@MGLL Hello. The active organization is recorded user session note of Keycloak, not into user attribute. And then, the protocol mapper automatically inject the "active-organization" claim into token if you enrolled the protocol mapper to the client scope. You can choose where to put (id token or access token) at the mapper setting of Keycloak.

스크린샷 2023-12-27 오전 10 36 28

Above is the example that the access token holds the claim of active organization. You can use the access token claim in the resource server to check the authority.

Switching the organization is not provided yet. If someone want to switch the organization, they need to login again.

For your information, I've implemented custom RealmResourceProvider that provides switching organization function by changing the user session of Keycloak. I confirmed the user session note of Keycloak was changed after applying the custom RealmResourceProvider but the access token still has the former selected organization. Switching the organization is more complicated problem. It is related with reissue of token of keycloak. if you have any clue to solve this problem, just let me know. It would be really helpful.

endpoint-spi.zip

MGLL commented 9 months ago

@millwheel Thanks, I will have a deeper look a bit later (with Keycloak docs & javadocs). But from what I see, you return just a success message, did you try to build & return a new access token with that?

Either relying on TokenManager (need to into Javadocs to be sure) or by building a new AccessToken from the received AccessToken data like:

... get current access token from header and decode it to get values...

AccessToken accessToken = new AccessToken();
accessToken.type(TokenUtil.TOKEN_TYPE_BEARER);

... map data from received token into new token...
... don't forget to set new active organization...

String signatureAlgorithm = session.tokens().signatureAlgorithm(accessToken.getCategory());
SignatureProvider signatureProvider = session.getProvider(SignatureProvider.class, signatureAlgorithm);
SignatureSignerContext signer = signatureProvider.signer();
String newAccessToken = new JWSBuilder().kid(signer.getKid()).type("JWT").jsonContent(accessToken).sign(signer);

... return success message with new access token...

And on application FE or BE side, you use this new token for next process.

It requires more works, but it's one possible solution, I think.

EDIT: Did you try to get a new access token with the refresh token application side after switching organization?

If it works (meaning you get the new state in the access token), you can provide both access token and refresh token in headers for this switch organization api call and in your flow, after updating session, generate a new access token with the refresh token and return it in response body.

xgp commented 7 months ago

Amazing addition @MGLL