mauriciovigolo / keycloak-angular

Easy Keycloak setup for Angular applications.
MIT License
724 stars 278 forks source link

Add support for Keycloak authorization endpoints #32

Open snowping opened 6 years ago

snowping commented 6 years ago

Are there any plans to support the authorization endpoints of Keycloak. If we activate Policy Enforcement on an API Backend we need to convert the Access Bearer Token to a Requesting Party Token (RPT) in order to get authorized.

With keycloak there are two options:

mauriciovigolo commented 6 years ago

Hi @snowping,

Currently I'm working on the next version of the library and I will take a closer look to this topic. As soon as I evaluate this I will let you know.

Thanks!

marcelnem commented 6 years ago

For supporting the authorisation APIs, a modified Http Interceptor is necessary. The interceptor should add authorization header to each request which goes to REST API. When Keycloak Policy enforcer returns unauthorized, it also returns URL on which the client can get the requesting party token containing necessary permissions. The example is here: https://github.com/keycloak/keycloak/blob/1b45ab260175c7bd3391f0c0ad7f41ac5566d602/examples/authz/photoz/photoz-html5-client/src/main/webapp/js/app.js#L134 The photoz app contains angular.js (not Angular 2+) client and Rest API protected by the policy enforcer.

Angular as a public client should not use standard authentication flow. Instead, it should use implicit flow. In implicit flow, the client receives an access code in the hash (#) segment in the redirect URL from the server. The hash segment is then only read by the keycloak.js. https://auth0.com/docs/api-auth/which-oauth-flow-to-use#is-the-client-a-native-app-or-a-spa-

The disadvantage of the implicit flow is that the client does not receive authorization token and thus cannot refresh access token, instead, it should log in again with keycloak. Logging in with keycloak should be done again in a hidden iframe to prevent disrupting the user. https://manfredsteyer.github.io/angular-oauth2-oidc/angular-oauth2-oidc/docs/additional-documentation/refreshing-a-token-(silent-refresh).html

mauriciovigolo commented 6 years ago

@marcelnem,

Thanks for your feedback and sharing. Lets move on this topic.

marcelnem commented 6 years ago

I am looking into it.

snowping commented 6 years ago

In my opinion the implicit flow should only be used if the SPA sends directly the username/pw combination to keycloak otherwise it is more secure to get a bearer token (incl. refresh token) using an authorization code.

as stated by @marcelnem using the bearer token we have basically two options

  1. Get a RPT directly from the Entitlement API without roundtrip over the resource server (API with PEP). This method is efficient but the SPA needs to know the client id of the resource server.
  2. Using UMA flow from the Authorization API as already explained by @marcelnem

Probably method 1 could be implemented without an interceptor as we only need to get RPT token on first login or when bearer token is about to expire.

Which method do you prefer and why?

marcelnem commented 6 years ago

I think we are talking about many issues now. Feel free to skip the italics for a TL;DR version.

Sending the username/pw is a separate issue. The default configuration is that a user logs in via Keycloak Login page and the client website never sees any username/pw. That is one of the security improvements which SSO brings. Trust Keycloak with your password because it is a well-tested software but not your own website which runs many javaScript libraries, CDN scripts, googles analytics, etc which could all steal the password. A similar principle is used when paying by a credit card, you are redirected to payment gate to enter your credit card details in an isolated environment without CDN scripts and many javascript libraries. You can enable the Direct Access Grants in Keycloak if you wish that your application sends user's username/pw on users' behalf.

What implicit and authorization code flow differ in is what happens after the login:

After user logs in via the Keycloak Login webpage, keycloak redirects back to the client website. Inside the redirect URL there is a hash parameter which contains either authorization code (in case of the standard flow, also called authorization code flow in Oauth2) or access code (in case of the implicit flow).

In authorization code flow (keycloak calls it a standard flow) , the idea is that the authorization code is used by a confidential client (e.g. backend). The confidential client has a client secret. The confidential client can exchange the authorization code + its client secret for an access code and refresh token. Even when somebody steals authorization code from the browser, they can not do anything with it because they do not have the client secret which is saved in the backend. The refresh token is saved also in the backend and can be then used for a long time to get new access tokens (which are short-lived). This is not the SPA anymore since we need to somehow pair the SPA with the refresh token in the backend (e.g. using session cookie).

Using authorization code (standard) flow is not recommended for a public client such as SPA, because a public client (SPA) can not hold a secret and it is possible to steal the refresh token from the browser by malicious software running on the computer of the user.

In implicit flow, the user logs in via Keycloak login page and receives the access code directly which is short lived.

It is up to a developer to consider the security risks of using authorization code flow vs implicit flow in SPA. In both cases, we will end up having an access token. Obtaining and refreshing access token is already implemented by keycloak-js for the standard flow. For implicit flow, only obtaining is implemented, and refreshing is not. There is no refresh token in implicit flow, so the way to obtain a new access token is to open the keycloak login page (no login will be required as long as Keycloak SSO session is alive) inside a hidden iframe and send the new access token to parent iframe using Window.postMessage(). This can be opened as a separate GitHub issue "Silent login for implicit flow."


But I propose to focus on the situation when the SPA already has an access token, regardless of how it was obtained. For testing purposes, standard flow which is already implemented could be used. Then the goal is to obtain an RPT.

When the Resource server is protected by the Keycloak Enforcer, the Access token is never sent to the Resource server, only RPT is sent to the Resource Server. The access token is sent to Keycloak in order to obtain an RPT. But this is handled by kecloak-authz.js. When the Resource server is protected by the Keycloak Enforcer, kecloak-authz.js automatically recognizes which API (Entitlement vs Authorization) is used and constructs the appropriate request. Obtaining an RPT is implemented in keycloak-authz which provides the KeycloakAuthorization class. (documentation: http://www.keycloak.org/docs/latest/authorization_services/index.html#_enforcer_js_adapter)

I would create a new type of interceptor, e.g. keycloak-rpt-interceptor. Instead of the access token, it would attach an RPT token to every HTTP request which is sent to the resource servers. RPT token with so-far obtained permissions can be read from KeycloakAuthorization.rpt variable. The purpose of the keycloak-rpt-interceptor is to be able to send requests to Resource servers protected by a keycloak policy enforcer.

keycloak-rpt-interceptor should not only add the RPT to the request but it should also check the response from the resource server. If the response is 401 Unauthorised, then keycloak-rpt-interceptor calls the KeycloakAuthorization.authorize(permissionTicketOrEntitlementURI) method which returns a sufficient RPT or deny or error. PermissionTicketOrEntitlementURI is in header of the previous "401 response" from the resource server. One can read it as response.headers.get('www-authenticate'). KeycloakAuthorization.authorize() does it's magic, the new RPT is obtained and the original request should be resent again, with the updated RPT. The updated RPT is available in KeycloakAuthorization.rpt variable.

*before calling KeycloakAuthorization.authorize(wwwAuthenticateHeader) one should check the expiration of the access token and if needed refresh the access token, since KeycloakAuthorization sends the access token to keycloak Authorisation API.

marcelnem commented 6 years ago

@snowping in case of method 1 (Entitlement API), the client can obtain an RPT with all permissions on the first try (if you do not specify which permissions you want, the Entitlement API returns all of them for the authenticated user), then KeycloakAuthorization.rpt can be used for all subsequent requests.

One still needs an interceptor to add RPT obtained via Entitlement API to each request made to a resource server protected by Keycloak enforcer.

marcelnem commented 6 years ago

In previous comments, I described how I understand the documentation and flows after playing with keycloak photoz sample app. Please comment too, and try to challenge it. I will see if I can implement the keycloak-rpt-interceptor.

marcelnem commented 6 years ago

I forked the repo, currently got stuck trying to import keycloak-authz (KeycloakAuthorization) from the keycloak-js module into keycloak-angular. Anybody managed to import the KeycloakAuthorization?

mauriciovigolo commented 6 years ago

@marcelnem and @snowping,

This topic is new for me and I'm taking a look at the keycloak documentation and the links you sent me. I will help you soon, ok? Nice to see the discussion and the work you doing.

Thanks!

snowping commented 6 years ago

@marcelnem Thanks for your comments. I have indeed mixed up two different things (direct grant vs. implicit flow). I agree with your description of the flow and would also focus on standard flow for now. I'll have a look at the keycloak-authz...

marcelnem commented 6 years ago

I managed to import it as import * as KeycloakAuthorization from 'keycloak-js/dist/keycloak-authz';

Also, I think there is a bug in the line number 21 of file keycloak-authz.d.ts. I had to edit the keycloak-authz.d.ts file in node_modules in order for the keycloak-angular library to compile.

it should be import * as Keycloak from 'keycloak-js'; instead of import * as Keycloak from 'keycloak';

mauriciovigolo commented 6 years ago

@marcelnem and @snowping,

I've studied this topic and is indeed a very nice feature to include on this library. I'm working on version 2.0.0 which will include a bundle of new features and this would be a very nice one!

For easier talking, this project has now a slack workplace at https://keycloak-angular.slack.com. For invitations please use this link: https://slackin-iijwrzzihr.now.sh.

I would like to thank you for all the research and work done so far. Let's move on this topic and make it happen!

MumblesNZ commented 5 years ago

Hey guys, was there any more movement on this enhancement?

mauriciovigolo commented 5 years ago

Hi @MumblesNZ, This topic is in standby but I will manage to finish it. It is an important feature and I left it stopped.

Marcel developed this feature and it is possible to merge that branch, but before I will take a look at the latest version of keycloak-js. It seems to have some enhancements on this topic. I will keep you in touch.

iceman91176 commented 5 years ago

Hi all, we are also interested in this feature. Is there anything we can do to get it integrated ? Thanks for you great work.

marcelnem commented 5 years ago

few things changed since the issue was opened

implicit flow is not recommended for single page applications in keycloak 7 (next release), there should be an implementation for Javascript adapter with PKCE which will be a recommended method for single page applications

RPT is not so necessary anymore, you can use keycloak policy enforcer to obtain RPT on a behalf of the client. There is still a branch that I implemented which has RPT and we were using it and it worked for us, but we do not use it anymore, we reconfigured our app to use keycloak policy enforcer to obtain RPT on behalf of our angular app. So we are using a standard release now, without RPT.

What exactly is your problem?

iceman91176 commented 5 years ago

Well, actually we need a way to check if a user is allowed to access resources/scopes from within our angular app. The backend/resource is already protected with policy enforcement, but the frontend is not. We want to control if a user is allowed to navigate to a certain route, or can see certain content.

So you basically recommend to do it like this -> https://www.keycloak.org/docs/latest/authorization_services/index.html#obtaining-entitlements ?

MumblesNZ commented 5 years ago

We also require the RPT on the angular side so we have access to the permissions object. We are using the permissions object to switch which angular elements are available to the user in our angular app. To me it seems logical to use keycloak as the single source of truth for what a user can access, both on the frontend and the backend. It would be interesting to know how others handle hiding UI elements which the user isn't allowed access to on the front end if they don't have access to this RPT in their angular client.

We also use the enforcer on the backend, but considering we already need the RPT on the frontend to configure our UI elements, it makes sense to pass this to our resource servers instead of the auth token so the enforcer on the backend doesn't also need to make a request to keycloak to get the RPT.

An added bonus of having the RPT in the angular app is it provides built in cache for the lifespan of the RPT token, as the enforcer on the resource server doesn't query the keycloak server if it is passed a valid RPT.

MumblesNZ commented 4 years ago

Following on from my comment above. The solution we implemented was to call the entitlement endpoint directly (there is an Entitlement function in keycloak-authz.js - https://www.keycloak.org/docs/6.0/authorization_services/#obtaining-entitlements, but we called it using the REST endpoint as we also wanted the refresh token).

As we are moving through our angular app, we request the permissions we require to this entitlements endpoint and store them in our local permissions object. If there is a permission that we need to persist (it gets called often so caching it on the client side provides benefit), we overwrite the keycloak token & refresh token with the RPT token, so when keycloak does the refresh token exchange the RPT is persisted.