[keycloak-user] Policy Enforcing for nodejs REST Api
Pedro Igor Silva
psilva at redhat.com
Tue Jun 4 11:23:43 EDT 2019
On Tue, Jun 4, 2019 at 11:46 AM Jahn, Lasse <
Lasse.Jahn at student.hpi.uni-potsdam.de> wrote:
> Hmm ok,
> But is the decision maid in keycloak or inside the keycloak Adapter ?
>
The decision is from the server but the enforcement is done by the adapter.
>
> I don’t really get why the adapter has to fetch the resources ? Couldn’t
> the adapter send the requested url from req object and the decision which
> resource is meant happens in the keycloak Service itself?!
>
Your point is correct, but our authorization request is based on the
resource/scopes you want to evaluate permissions too. Subject to
improvements though.
>
> So the policy, permissions, resources only stay inside of keycloak and the
> adapter only receives a access grant or access deny which he than enforces.
> Or in other words, that the adapter is only the PEP (policy enforcement
> point) and keycloak service is the PDP (policy decision point).
>
That is how it works. By fetching resources, I mean obtaining from Keycloak
the mappings for URIs <-> Resources IDs so that we can send authorization
requests accordingly.
>
> On 4. Jun 2019, at 14:47, Pedro Igor Silva <psilva at redhat.com> wrote:
>
>
>
> On Tue, Jun 4, 2019 at 9:28 AM Jahn, Lasse <
> Lasse.Jahn at student.hpi.uni-potsdam.de> wrote:
>
>> So just to make it sure, if I use the node adapter the resource URIs in
>> the keycloak admin console are not used yet?
>>
>
> Yes. That is why you need to use the enforcer in the route so that you
> associate a resource with the route/path.
>
> In other adapters, the enforcer is capable of fetching and caching
> resources from the server based on the requested URI. So you don't need to
> do this association manually.
>
>
>>
>> I would have thought that the backend sends a request to keycloak sending
>> the
>> - bearer token (for all credentials and user information...)
>> - request object (to have the requested resource)
>>
>> And then keycloak evaluates and only answers grant or deny ?!
>>
>
>> Is this decision maid inside the adapter or keycloak?
>>
>
> Yes, that is how it works. The missing part is the enforcer using the
> requested URI to match a resource in Keycloak so that permissions can be
> enforced.
>
>>
>> On 4. Jun 2019, at 14:06, Pedro Igor Silva <psilva at redhat.com> wrote:
>>
>> Differently than other adapters, the NodeJS adapter does not fetch
>> resources from the server, so you need to use the enforcer on each route:
>>
>> app.get('/api/users', keycloak.enforcer(['users'])
>> app.get('/api/devices', keycloak.enforcer(['devices'])
>>
>> Please, create an RFE if you the current behavior is not enough for you.
>>
>> On Tue, Jun 4, 2019 at 5:23 AM Lasse Jahn <lasse.jahn at student.hpi.de>
>> wrote:
>>
>>> Hey Pedro,
>>>
>>> sorry for the really late reply. There've been some other issues I had
>>> to fix first before I could come back to authorization. But now I try to
>>> get this done.
>>>
>>> Unfortunately I don't really get the thing with the resources and the
>>> regarding URIs.
>>>
>>> I want the keycloak enforcer middleware only called at the one point,
>>> like I explained. Based on the called route (e.g. /api/users or
>>> api/devices) I would like to have only the permissions of the resource
>>> evaluated.
>>>
>>> I guess somehow I just miss a thing and it should be easy possible.
>>>
>>>
>>> What I did:
>>> 1. Created 2 resources (users, devices with URIs "/api/users" or
>>> "/api/devices"
>>> 2. Created 2 permission and policy (users grant always, devices deny
>>> always)
>>> 3. Added the keycloak enforcer middleware before the router.
>>>
>>> ****
>>> app.use('/api', keycloak.enforcer(['users', 'devices']), routes);
>>> ****
>>>
>>> Unfortunately when I access /api/devices this is allowed.
>>>
>>> I would like to create resources on the client and have one policy per
>>> each to decide. Is it possible, that the enforcer checks which resource is
>>> requested and uses only that one.
>>>
>>> If not what is the URI of a resource for?
>>>
>>>
>>> Regards Lasse
>>>
>>>
>>> On 15.05.19 19:55, Pedro Igor Silva wrote:
>>>
>>>
>>>
>>> On Wed, May 15, 2019 at 8:52 AM Lasse Jahn <lasse.jahn at student.hpi.de>
>>> wrote:
>>>
>>>> Hi Pedro,
>>>>
>>>> thanks for the quick reply. So I got it working now, that the resource
>>>> I created is enforcing the one policy. For a single resource this is great.
>>>>
>>>> Later on I would like to have an multi tenant solution, short
>>>> explanation what I mean:
>>>>
>>>> Different companies have the same functionality but maybe want to
>>>> change the restriction for there self. But still with only one backend
>>>> application running. So each company should get one realm with the backend
>>>> application registered as a client. When they call the api the backend
>>>> should enforce the policies of the company specific client.
>>>>
>>>> Therefore I have 2 questions:
>>>>
>>>> 1. Is it possible to configure the enforcer to enforce all policies for
>>>> all resources only depending on the requested on the path. So I only to
>>>> have to add the middleware once before the express router. So for example
>>>> we have a route /api/devices and /api/users (GET,POST,DELETE each). Both
>>>> are represented by a resource in the keycloak admin console.
>>>>
>>>> I would like to have something like this:
>>>>
>>>> router.js
>>>>
>>>> ****
>>>>
>>>> const express = require('express');
>>>> const router = express.Router();
>>>> const users = require('../controllers/users.controller');
>>>> const devices = require('../controllers/devices.controller');
>>>>
>>>> router.post('/users/', users.create);
>>>> router.delete('/users/', users.deleteAll);
>>>> router.get('/users/', users.findAll);
>>>>
>>>> router.post('/devices/', devies.create);
>>>> router.delete('/devices/', devicese.deleteAll);
>>>> router.get('/devices/', devices.findAll);
>>>>
>>>> module.exports = router;
>>>>
>>>> ****
>>>>
>>>>
>>>> app.js
>>>>
>>>> ****
>>>>
>>>> .... //all from before
>>>>
>>>> app.use('/api', keycloak.enforcer(__SOME_CONFIG__), routes);
>>>>
>>>> ****
>>>>
>>>>
>>>> I dont want to write keycloak.enforcer(...) to each line of users or
>>>> devices...
>>>>
>>>> Maybe this can done by the claims and the context information? But if
>>>> yes I don't get how.
>>>>
>>> Yes, by using claims you are allowed to use them in your policies. Here
>>> is an example:
>>> https://github.com/keycloak/keycloak-quickstarts/blob/latest/app-authz-rest-employee/config/quickstart-realm.json#L90.
>>> More details here
>>> https://www.keycloak.org/docs/latest/authorization_services/index.html#examples
>>> .
>>>
>>> Your keycloak.enforce would be similar to
>>> https://github.com/keycloak/keycloak-nodejs-connect/blob/master/test/fixtures/node-console/index.js#L177
>>> .
>>>
>>>>
>>>> 2. For now the solution is only single tenant, but If I want to have it
>>>> multi tenant and have realm per company with similar clients (only
>>>> different in policies and permissions). Do you have an idea how I can solve
>>>> the problem that the keycloak object is configured realm specific?
>>>> I would probably build a middleware which checks for a custom HTTP
>>>> header or looks for a subdomain for referencing the company. Depending on
>>>> the company I would set the keycloak object.
>>>> Do you think this can work ? Or do you have a better idea?
>>>>
>>> AFAIK, this is how you do it. So that accordingly with the request you
>>> build a new Keycloak object using a specific realm.
>>>
>>>
>>>>
>>>> Some suggestions for your documentation:
>>>>
>>>> - Could you somewhere describe what the middleware option protected is
>>>> doing? The Logout, ... options are explained, but the protected I couldn't
>>>> find.
>>>> - Your default resource is called 'Default Resource' but in the example
>>>> the resource is renamed to 'resource' the rest is untouched and default
>>>> config. Maybe a comment or adjustment of the example might be helpful.
>>>>
>>> Thanks for the feedback. Feel free to create a JIRA so that we can track
>>> and plan the improvements you are proposing.
>>>
>>>>
>>>> Regards,
>>>> Lasse
>>>> On 14.05.19 20:33, Pedro Igor Silva wrote:
>>>>
>>>> Hi,
>>>>
>>>> We've added more docs to NodeJS PEP recently [1]. They should be
>>>> available in the next release. Please, let me know if that is enough or if
>>>> we need to add more information.
>>>>
>>>> In your case, this code:
>>>>
>>>> app.use('/api', keycloak.enforcer({WHAT_COMES_HERE}), routes);
>>>>
>>>> Would be:
>>>>
>>>> app.use('/api', keycloak.enforcer('{resource_name}:{resource_scope}'),
>>>> routes);
>>>>
>>>> If you have a resource in Keycloak called "foo" and a scope associated
>>>> with this resource called "bar", the code would be:
>>>>
>>>> app.use('/api', keycloak.enforcer('foo:bar'), routes);
>>>>
>>>> Hope it helps.
>>>>
>>>> [1] https://github.com/keycloak/keycloak-documentation/pull/654
>>>>
>>>> On Tue, May 14, 2019 at 1:25 PM Jahn, Lasse wrote:
>>>>
>>>>> Hello,
>>>>>
>>>>> It's the first time writing to keycloak mailing list (I hope this is
>>>>> the correct one?) so excuse if I forget to provide some information or any
>>>>> other mistakes ..
>>>>> Sorry for the text wall.
>>>>>
>>>>> Shortly what I try to do (maybe I got something completely wrong):
>>>>> I create a backend (node.js Bearer Only) which shall offer an REST
>>>>> api. Partially it is used via a frontend (keycloak-clients) or directly by
>>>>> some devices.
>>>>> In general I try to create an application with a lot of CRUD. User
>>>>> Management is done in keycloak and only I forward these requests to the
>>>>> admin REST Api. Other stuff like the devices ... I store in a separate
>>>>> database.
>>>>> So the backend is the abstraction layer for frontend and other
>>>>> use-cases.
>>>>>
>>>>> So far so good, but for the beginning it was enough to check weather
>>>>> the request comes from an authenticated person or not, so all handled via
>>>>> keycloak.protect() The Token from the authenticated person was passed
>>>>> But now I'd want to offer different authorization level (can differ
>>>>> due to reasons of multitenancy, why I want to solve this via policies and
>>>>> co in admin-console inside the client configuariton) because the normal
>>>>> user shall have access to only some routes and the management shall have
>>>>> full access to the api, but of course don't need the keycloak admin access.
>>>>> So I enabled the service account for my backend client and gave this
>>>>> one the realm-admin role so the client has access to everything and I can
>>>>> handle the authorization inside the backend client it self (using policies,
>>>>> permissions, .. inside the admin-console).
>>>>> (Just in case no one gets what I'm talking about. Fixing [1] should
>>>>> help me fixing my issue I guess)
>>>>>
>>>>> Setup
>>>>> - node.js application using express
>>>>> - registered as single client in keycloak admin-console (confidential,
>>>>> but config inside the code is bearer-only)
>>>>> - Keycloak is running in a docker-container (version 4.5)
>>>>> - all services are running in a docker-compose network and are behind
>>>>> a reverse proxy for common uri
>>>>> - enabled Authorization in client and changed the default policy to
>>>>> Negative to always deny => to see if it is enforced)
>>>>>
>>>>> My Problem
>>>>> I don't understand how to use the policies, permissions and Co I
>>>>> created in the admin-console inside the backend it self. How do I enforce
>>>>> that these are used?
>>>>> I tried to check different examples and documentation, but could get
>>>>> it working.
>>>>> The last thing I found was that the entitlement api was removed, but a
>>>>> policy-enforcer was added to the nodejs adapter. In the documentation for
>>>>> the policy-enforcer [2] I couldn't find a documentation of the middleware
>>>>> (keycloak.enforcer({}) [3][4]).
>>>>>
>>>>> My Code
>>>>>
>>>>> *****
>>>>> app.js
>>>>>
>>>>> const express = require('express');
>>>>> const app = express();
>>>>> const Keycloak = require('keycloak-connect');
>>>>> const session = require('express-session');
>>>>> const routes = require('./routes/index');
>>>>>
>>>>>
>>>>> const kcConfig = {
>>>>> 'realm': 'master',
>>>>> 'bearer-only': true,
>>>>> 'auth-server-url': `https://DOMAIN/auth<https://domain/auth>`,
>>>>> 'ssl-required': 'all',
>>>>> 'resource': 'fm-backend',
>>>>> 'credentials': {
>>>>> secret: 'SOME_SECRET',
>>>>> },
>>>>> 'confidential-port': 0,
>>>>> 'policy-enforcer': { //tried with
>>>>> an without this, changed nothing
>>>>> 'enforcement-mode': 'ENFORCING',
>>>>> },
>>>>> };
>>>>>
>>>>> const memoryStore = new session.MemoryStore();
>>>>> const keycloak = new Keycloak({ memoryStore }, kcConfig);
>>>>>
>>>>> app.use(keycloak.middleware({ logout: '/api/logout', protected:
>>>>> '/api/gates' }));
>>>>>
>>>>> // used before, worked for well for authentication
>>>>> app.use('/api', keycloak.protect(), routes);
>>>>>
>>>>> // now unfortunately I don't understand how to use keycloak.enforcer()
>>>>> middleware
>>>>> app.use('/api', keycloak.enforcer({WHAT_COMES_HERE}), routes);
>>>>>
>>>>> module.exports = app;
>>>>>
>>>>> *****
>>>>>
>>>>> [1]
>>>>> https://stackoverflow.com/questions/53722033/how-to-enable-policy-enforcing-in-keycloak-for-node-js-application
>>>>> [2]
>>>>> https://keycloak-docs.github.io/deploy-docs/dev/master/authorization_services/index.html#_enforcer_overview
>>>>> [3]
>>>>> https://github.com/keycloak/keycloak-documentation/blob/master/securing_apps/topics/oidc/nodejs-adapter.adoc
>>>>> [4]
>>>>> https://github.com/keycloak/keycloak-nodejs-connect/blob/master/example/index.js
>>>>>
>>>>>
>>>>> Any Help is appreciated :)
>>>>>
>>>>>
>>>>> With kind regards
>>>>> Lasse
>>>>> _______________________________________________
>>>>> keycloak-user mailing list
>>>>> keycloak-user at lists.jboss.org
>>>>> https://lists.jboss.org/mailman/listinfo/keycloak-user
>>>>>
>>>>
More information about the keycloak-user
mailing list