Keycloak JS client and confidential clients

The keycloak-js client no longer supports the ability to add a keycloak clientSecret, this means for Front End JS Apps, you can only use a Public keycloak client.

Public keycloak clients do not allow you to configure fine-grained authorization services, i.e. Resource, Scopes, Policies, Permissions.

I can only see four options.

For Front End JS clients, just accept that only simple authorization through roles is available.
Access Keycloak from the Client via an API, although for the Front End client, that means trusting something other than Keycloak, which adds security holes.
Find an alternative to Front End JS
Find an alternative to Keycloak

Am I missing something? How are others getting around this problem?

2 Likes

Is this a new thing, @simonjcarr ? Iā€™m using Keycloak 9.0.2 and I can use fine-grained permissions with a public client.

Interesting. I have the same conundrum as @simonjcarr. Since we are forced to set the access type to be public, I lose access to the Authorization tab within my client. @sharpedavid Iā€™m curious about your setup, would you mind sharing it? Perhaps @simonjcarr and I are missing something.

Iā€™m using version 13.0.0. However, when looking for what version Iā€™m running I realized the ADMIN_FINE_GRAINED_AUTHZ feature is disabled for me but it also appears to be a preview feature which are disabled by default. Do you have the ADMIN_FINE_GRAINED_AUTHZ feature enabled?

Hereā€™s my setup:
My current setup in Keycloak is as follows:

  • Realm: admin-service
  • Client: admin-service-api
    • Access Type: confidential (because I want to use authorization to restrict api requests according to permission levels)
    • Root URL: http://localhost:8080/
    • Valid Redirect URIs: http://localhost:8080/*
  • Roles:
    • admin
  • Users:
    • test-user

My authorization setup for admin-service-api is as follows:

  • Resources: Books Resource
    • Uri (these are my API endpoints):
      • /v1/books
      • /v1/books/{id}
    • Scopes:
      • books:delete
      • books:create
      • books:update
      • books:read
  • Policies:
    • Default Policy (probably not needed?)
    • Books Policy:
      • Realm roles:
        • admin (the required checkbox is not checked)
  • Permissions:
    • Default Permission (probably not needed?)
    • Books Resource Permission:
      • Resources: Books Resource
      • Apply Policy: Books Policy

So with this setup, I have restricted my API (which is written in GoLang) to only allow requests if the requesting user has the appropriate permissions by making a request to the Keycloak API via https://my.auth.server/auth/realms/{{realm_name}}/protocol/openid-connect/token to retrieve the users access token and a list of the users permissions.

From there I can use this access token to make requests to my API to create/read/update/delete books so long as my test-user has the admin role. If my user does not have the admin role, the user is presented with an unauthorized message (401).

Hi @mdelez , I realize now that I was thinking of a different feature. When @simonjcarr referred to ā€œfine-grained authorization servicesā€, I assumed he meant the experimental feature Fine Grain Admin Permissions, but I see now he just meant the basic Authorization services, which I didnā€™t realize Keycloak also referred to as ā€œfine-grainedā€ in their documentation and UI. My mistake.

Youā€™re right, the Authorization tab is available only for Confidential clients. I donā€™t really know for certain, but I feel like this is a logical requirement. Authorization requires enforcement. If you enable Authorization, youā€™re expected to do policy enforcement in your application. You canā€™t securely do policy enforcement in a front-end application, because the user has complete access to your policy enforcement code.

I think if you wanted to ā€œcheatā€, you could use a Confidential client on the front end if you can find a Javascript OIDC adapter that allows you to specify the secret, though I suspect some do not to prevent this kind of insecure implementation.

If you want to ā€œdo it rightā€, I guess you need two clients. A public client for your front-end, and a confidential client for your back-end.

Not really an expert though: Iā€™m just guessing thatā€™s why this is the case.

Hi @sharpedavid, no worries about the confusion, Iā€™ve come to realize thatā€™s quite normal when working with Keycloak. Their documentation is extensive but at the cost of being confusing.

Iā€™m currently trying the two clients method (actually I was last week but Iā€™m still struggling). Currently I have my admin-service-api set up like it is above and I also have another client called ā€œadmin-uiā€ which is my frontend and this client has an access type of public. So with this, I can use the js-adapter to authenticate my user and I get an access token via https://my.auth.server/auth/realms/{{realm_name}}/protocol/openid-connect/token. Easy-peasy. The problem Iā€™m facing, is how can I then use this access token to make requests to my API? Since my API is protected via a different client, if I use the access token I got from my admin-ui client, I am unable to retrieve a list of scopes that my user has access to via https://my.auth.server/auth/realms/{{realm_name}}/protocol/openid-connect/token

For obtaining the userā€™s scopes I use the endpoint above and this as the request body:
(content-type is x-www-form-urlencoded)
grant_type: urn:ietf:params:oauth:grant-type:uma-ticket
audience: admin-service-api
response_mode: permissions

Itā€™s clear to me that I should have one client for Authentication, which will be public and one client for Authorization, which will be confidential. Whatā€™s super unclear to me is how I can use these in conjunction with one another.

Hi @mdelez , Iā€™m not an expert when it comes to the Authorization features of Keycloak using resources and scopes. Iā€™ve enabled Authorization features to play around a bit, but I rely on role-based access in production.

For basic role-based access, my set-up looks like this:

  • Confidential backend client. Has roles A, B, C, D.
  • Public frontend client. Has no roles of its own, but in the ā€œScopeā€ tab you permit the backend roles (alternatively enable ā€œFull Scope Allowedā€).
  • The user gets assigned roles for the backend client. When they authenticate with the frontend client, their token will include the backend roles.

Sorry if this isnā€™t helpful, but good luck. My advice is to avoid enabling Authorization unless youā€™re certain you want it. Itā€™s much easier to get started with roles.

I eventually got it working but I wasnā€™t understanding how it was working so I switched to something similar to your set up. Perhaps Iā€™ll revisit fine grained authorization one day but it sure is difficult to understand how it all works. Basic role-based access is much simpler to understand :slight_smile:

Iā€™m using Keycloak JS Client for user to login with browser. There is a backend app which is protected by access token and use keycloak token introspect. But it doesnā€™t work when the client set to public (I check the token introspect api, which work if it set to confidential, sending the client secret). This seems to contradict: you can only use public client with keycloak JS client and you can only use confidential client for token introspect endpoint. Can someone enlighten me here?

front application are insecure by design, thatā€™s why the ā€˜publicā€™ mode exists. You cannot safely store a secret in a front application. The OIDC security there is only based on the allowed domains and origins. A frotn application should never use a non-public client.

Your public js application should communicate with a backend application, this backend application can use ā€˜confidentialā€™ or ā€˜bearer-onlyā€™ mode. The bearer-only mode is especially made for such backends, as they receive tokens from ajax requests sent by front application, they do not need to redirect the user to the login page when the token is wrong (as a confidential application would do), but send errors instead. Iā€™m pretty sure a bearerOnly application could use the introspection endpoint.

You can also add ā€˜audienceā€™ (ā€˜audā€™) management with the ā€˜scopeā€™ definition, and ensure that the access token received by the front application contains audiences. these access_tokens are used for communications between the front app and the backends, and the audience field is a list of valid client_id for this token (so it can list the valid backends).

On the other side a backend application should never use a ā€˜publicā€™ client. Thatā€™s only for front applications, with validated redirects and logins.

I hope this explains why public clients have some limitations.

1 Like

Iā€™m facing the same problem. Have you solved your problem yet?