How to authenticate curl (or Postman, or Ajax) call with Spring Cloud Gateway and Keycloak - keycloak

I'm using Spring Cloud Gateway with Keycloak, and authentication works from browser.
But, there is no Authorization header with the token, but SESSION cookie instead.
I don't want to use cookies at all, but token instead.
Currently, if I post to http://localhost:8080/auth/realms/myrealm/protocol/openid-connect/token to get tokens and try to put access_token in Authorization header with Bearer it doesn't work, but returns login page.
Also if I add keycloak.js to browser page, and after successful login try to add xhttp.setRequestHeader('Authorization', 'Bearer ' + keycloak.token);
to Ajax call, it again returns login page.
So how to use Spring Cloud Gateway with Keycloak, from any client with provided valid token at all?
Here is my gateway configuration, and web app behind it with /web/ route doesn't have any security applied. Keycloak is configured with basic public client.
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http,
ReactiveClientRegistrationRepository clientRegistrationRepository) {
http.oauth2Login();
http.logout(logout -> logout
.logoutSuccessHandler(new OidcClientInitiatedServerLogoutSuccessHandler(clientRegistrationRepository)));
http.authorizeExchange().pathMatchers("/web/private").authenticated()
.pathMatchers("/web/**").permitAll()
.anyExchange().authenticated();
http.headers().frameOptions().mode(Mode.SAMEORIGIN);
http.csrf().disable();
return http.build();
}

If you want to make requests to Spring Gateway with access token you need to make it a resource server: https://stackoverflow.com/a/66923312/11122338

Related

Oauth2 Login in Spring Cloud Gateway using KeyCloak

I implemented oauth2 in Spring cloud gateway using KeyCLoak. Now I want all the request are permitted except those which have "/user" in the url. Following is my security configuration
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.cors()
.and()
.csrf()
.disable()
.authorizeExchange()
.pathMatchers("**/user/**").authenticated()
.anyExchange().permitAll()
.and()
.oauth2Login();
return http.build();
}
I am expecting when user enter the url "http://localhost:8066/user" it will redirect them to keycloak login page, but it is not working,it is not redirecting to the login page of Keycloak.
Read the doc for AnthPathMatcher and more generally what valid ant path are.
My guess is what you want actually is .pathMatchers("/user/**").authenticated()

K8S Dashboard login with url

I'm running an eks cluster, installed k8s dashboard etc. All works fine, I can login in the UI in
http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/#/login
Is there a way for me to pass the token via the url so I won't need a human to do this?
Thanks!
Based on official documentation it is impossible to put your authentication token in URL.
As of release 1.7 Dashboard supports user authentication based on:
Authorization: Bearer <token> header passed in every request to Dashboard. Supported from release 1.6. Has the highest priority. If present, login view will not be shown.
Bearer Token that can be used on Dashboard login view.
Username/password that can be used on Dashboard login view.
Kubeconfig file that can be used on Dashboard login view.
As you can see, only the first option bypasses the Dashboard login view. So, what is Bearer Authentication?
Bearer authentication (also called token authentication) is an HTTP authentication scheme that involves security tokens called bearer tokens. The name “Bearer authentication” can be understood as “give access to the bearer of this token.” The bearer token is a cryptic string, usually generated by the server in response to a login request. The client must send this token in the Authorization header when making requests to protected resources:
You can find more information about Baerer Authentication here.
The question now is how you can include the authentication header in your request. There are many ways to achieve this:
curl command - example:
curl -H "Authorization: Bearer <TOKEN_VALUE>" <https://address-your-dashboard>
Postman application - here is good answer to set up authorization header with screenshots.
reverse proxy - you can be achieve this i.e. by configuring reverse proxy in front of Dashboard. Proxy will be responsible for authentication with identity provider and will pass generated token in request header to Dashboard. Note that Kubernetes API server needs to be configured properly to accept these tokens. You can read more about it here. You should know, that this method is potentially insecure due to Man In The Middle Attack when you are using http.
You can also read very good answers to the question how to sign in kubernetes dashboard.

Use Keycloak Gatekeeper in front of backend API

On a single page app (SPA) that runs on DOMAIN calls to DOMAIN/graphql are rerouted to the backend. Both the frontend and backend are secured via a Keycloak Gatekeeper instance.
The idea is that the frontend and backend share the kc-access token.
Now, the access token expires in the backend Gatekeeper. If the SPA is refreshed in the browser the frontend is rerouted to Keycloak and a fresh access token is required. But if there's no refresh, the POST requests to DOMAIN/graphql fail with a 307 status code when the token has expired. The browser does not know how to handle this. The browser logging gives an "{"error":"RESTEASY003065: Cannot consume content type"}". If the content-type header of the POST is removed the error is "no client_id provided", while the client_id is included in the query string.
Redirecting a POST request to Keycloak would probably not be the best solution. Cleaner would be if the backend refreshes it's access token itself.
This is what we tried by adding a session state store to the backend's Gatekeeper. We are using the following configuration:
- --discovery-url=DISCOVERY_URL
- --client-id=CLIENT_ID
- --client-secret=****
- --enable-refresh-tokens=true
- --encryption-key=0123456789012345
- --store-url=boltdb:///boltdb
- --listen=0.0.0.0:3001
- --verbose=true
- --redirection-url=REDIRECTION_URL
- --upstream-url=http://127.0.0.1:3000
This does create a /boltdb file in the Gatekeeper, but it does not seem to be used since the file does not change.
The backend's Gatekeeper gives the following logging:
|1.5716729131430433e+09|debug|keycloak-gatekeeper/session.go:51|found the user identity|{"id": "b5b659cd-148e-4f23-bf2f-28e6f207f6c7", "name": "piet", "email": "", "roles": "offline_access,dashboard_viewer,uma_authorization,account:manage-account,account:manage-account-links,account:view-profile", "groups": ""}|
|1.5716729131462774e+09|info|keycloak-gatekeeper/middleware.go:154|accces token for user has expired, attemping to refresh the token|{"client_ip": "****", "email": ""}|
|1.5716729131463811e+09|error|keycloak-gatekeeper/middleware.go:161|unable to find a refresh token for user|{"client_ip": "**", "email": "", "error": "no session state found"}|
So we are "unable to find a refresh token for user" because there is "no session state found" according to the logging.
Anybody any idea how to enable token refresh?
It doesn't look like a good design. Keycloak Gatekeeper uses grant code flow, which is not the best flow for SPA as you have discovered (it seems to be very hackish to read user identity provided by Gatekeeper in SPA case).
SPA uses Code Flow with PKCE or Implicit Flow and these flows use silent token renewal (and not refresh token). IMHO the best option will be to use the same client id in the frontend (SPA) and in the backend (e.g. API). But frontend will be protected by Code Flow with PKCE and it will handle own token renewal. Only backend will be protected by Gatekeeper (+ --no-redirects setting makes sense for API protection)
By also setting enable-refresh-tokens=true with the same encryption key in the Gatekeeper on the frontend the design works.
The user retrieves the frontend and is redirected to Keycloak. There an authorization code is obtained. This authorization code is exchanged by the frontend Gatekeeper for an access and refresh token that are put in a cookie on the frontend. When the backend is called with an expired access token the refresh token is decrypted and used to get a new access token.
The refresh token can expire or be invalidated. When a 401 is returend the frontend should refresh the page so the user is redirected to Keycloak.
More secure would be to store the tokens not in the frontend cookies, but in a shared store.

Validate oAuth 2 access token in APIGEE without VerifyOAuthTokens policy

We are using Apigee as our Authorization Server (AS) and we have a few Spring Restful services deployed in IBM Bluemix public cloud which acts as our Resource server (RS).
Each of the services has an equivalent proxy service configured in Apigee. For the proxy services, we have configured the VerifyOAuthTokens policy to verify the token passed by the user and return an error if invalid token is passed
The problem is, since our RS is in the public cloud (no plans or need of moving to a dedicated or private cloud) the api endpoints are open and can be invoked by anyone who knows the url.Though the expectation is everyone should call the apis via APIGEE proxies but we cannot force that since we are in public cloud and there are no options of opening ports coming from apigee or something. We would like to take the following approach to secure the api endpoints.
Accept the Authorization header for each call
Take the token and call a validate token service in Apigee
For 2, We are not able to find an APIGEE api which can validate an access token similar to say googles
https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=1/fFBGRNJru1FQd44AzqT3Zg
or Github's
GET /applications/:client_id/tokens/:access_token
Is there actually an external APIGEE service to validate a token?
If not, what would be the best way to make sure that only valid users with valid tokens can access the apis?
Thanks,
Tatha
Did you look at this post in the Apigee Community: Using third-party OAuth tokens
We did something similar to this but not using oauth tokens. We used Apigee to do a callout to a third party IDP (identity provider). The 3rd party IDP wasn't able to generate tokens but exposed a web service to authenticate the user. If the user was authenticated successfully (based on interpreting the result received back from the target endpoint webservice), then you tell Apigee that it was successful by setting the external authorization status to true (step #2 in the link).
NOTE: this has to be done inside an Assign Message Policy step PRIOR to the GenerateAccess token operation. Apigee interprets this as a successful authorization and then can generate a valid oauth token that the caller can then send along to access the protected API.

Accessing REST service after login within browser using oauth2 and spring security using java config

I have implemented Oauth2 using sparkl2 app. I am using spring-security as described in the sparkl2 app using java config. I can successfully get auth token using curl and i can invoke web service using curl.
My question is
How I can access my REST service within the same browser after login into my application? I am not sure what I am missing here?
Let me elaborate my question in more details. The way browser keep session after login and we can access any protected resource in the application, what is the best way to implement so that I can test my REST api from browser
spring security keeps it in session. Session id is stored in browser cookie, so its passed with each request to your service. Then spring security should take it and check if specific session(with user logged in) is allowed to hit this particular url.
I would start with configuring secure paths in your java config:
http.authorizeRequests().antMatchers().hasAnyRole(...)
or some other method instead antMatchers.
you probably have to log in user into spring security on some oauth callback, something like:
Authentication auth = new UsernamePasswordAuthenticationToken(user, null, authorities);
SecurityContextHolder.getContext().setAuthentication(auth);