This is related to keycloak clients. My frontend is connected to public client and backend is connected to confidential client.
I am able to login, get the code, as I am using response_type=code by turning on "Standard Flow Enabled".
This code redirects and returns me Idtoken, refreshtoken and token.
Now I need to communicate with backend which is confidential, I would like to authenticate user using some of the values which I have received from the frontend client.
How can I do that?
Here is my frontend and backend conf
FRONTEND
{
"realm": "xyz",
"auth-server-url": "http://localhost:8333/auth/",
"ssl-required": "external",
"resource": "frontend-app",
"public-client": true,
"confidential-port": 0,
"enable-cors": true
}
BACKEND
keycloak.auth-server-url=http://localhost:8333/auth
keycloak.realm=xyz
keycloak.resource=backend-app
keycloak.principal-attribute=preferred_username
keycloak.bearer-only=true
keycloak.credentials.secret=xxx-xxx-xxx
this is from realm setting
This might help somebody.
My backend service which is springboot project with spring security keycloakAuthenticationProvider does authenticate the token received from the frontend public client.
Call from frontend
axios({
method: 'GET',
url: '/api/authenticate',
headers:{'Authorization': 'Bearer '+keycloak.token+''}
}).then((r) => {
console.log("response",r)
})
Call to backend
#GetMapping("/api/authenticate")
public ResponseEntity<SecureUserDto> authenticate() {
String username = SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString();
User user = userRepository.findWithPrivilegesByUsername(username);
return ResponseEntity.ok();
}
But i still was not able to get it right on postman at ../token end point provided by keycloak server.
Anyways my work is done.
Related
I would like to programmatically register confidential OIDC client (the client is a backend service). I checked the keycloak document about it. And I use the “Initial access token“ approach as recommended.
After I created a "initial access token" on the UI console, I register a new client by:
POST https://my-keycloak-host/auth/realms/MyRealm/clients-registrations/default
Headers: Authorization: Bearer <initial access token>
{ "clientId": "my-client" }
The response contains a registrationAccessToken. But I expect to get a client secret. How can I get it? And what is the usage for that registrationAccessToken?
For some unknown reason Keycloak doesn't set secret properly. But you can define own secret with secret property in the payload (tested with Keycloak 16.1.1), e.g.:
{
"clientId": "my-client",
"secret": "my-secret"
}
Doc:
https://openid.net/specs/openid-connect-registration-1_0.html
https://www.keycloak.org/docs/latest/securing_apps/index.html#_client_registration
keycloak js version -> ^10.0.2
angular -> ^7.2.16
keycloak.json
{
"realm": "REALM",
"auth-server-url": "<auth-url>/auth/",
"ssl-required": "external",
"resource": "CLIENT_ID",
"verify-token-audience": true,
"credentials": {
"secret": "CLIENT_SECRET_KEY"
},
"use-resource-role-mappings": true,
"confidential-port": 0,
"policy-enforcer": {}
}
using in init like this
const keycloakAuth = Keycloak('keycloak.json');
keycloakAuth.init({ onLoad: 'login-required', checkLoginIframe: false })
when keycloak made this call <auth-url>/auth/realms/guavus/protocol/openid-connect/token than giving this error.
{"error":"unauthorized_client","error_description":"Client secret not provided in request"}
as per documentation removed credential support from javascript adapter
than what is the alternative of this and how to fix this error?
If this is an Angular Single Page Application (SPA), you should be using a public client (which will not use a client secret) per the Keycloak docs:
"One important thing to note about using client-side applications is that the client has to be a public client as there is no secure way to store client credentials in a client-side application."
https://www.keycloak.org/docs/latest/securing_apps/#_javascript_adapter
Yes, I got to know that they removed credential support from javascript adapter
https://www.keycloak.org/docs/latest/release_notes/#credentials-support-removed-from-the-javascript-adapter
https://github.com/keycloak/keycloak/commit/913056b2b2d39707347a39dddb7bdad69fe47cc3
and for javascript adapter they mentioned in document
To use the JavaScript adapter you must first create a client for your application in the Keycloak Administration Console. Make sure public is selected for Access Type.
I'm developing an app using Ionic Framework and generated a JHipster project for my backend. My JHipster project runs on an extra Server and is called via REST requests from my App. So my problem now is handling the CORS and CSRF configuration.
My JHipster project has its own frontend, which runs on the same domain and while testing I can reach my backend without any issues. However, when I want to call my backend on the server from my Ionic app my xsrf tokens wont update properly and, therefore, I cannot access my backend. I already tried several solutions from different stack overflow posts, but none of them worked for me.
For example:
Ionic using CORS and CSRF
Could not verify token
https://github.com/angular/angular/issues/18859
What I've done so far:
I enabled csrf in my SecurityConfiguration in my JHipster project
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling()
.authenticationEntryPoint(problemSupport)
.accessDeniedHandler(problemSupport)
added CORS configuration
cors:
allowed-origins: 'http://localhost:8100, ionic://localhost, http://localhost'
allowed-methods: 'POST, GET, OPTIONS, DELETE, PUT, HEAD'
allowed-headers: 'Origin, X-Requested-With, Content-Type, Accept, x-auth-token, Authorization, X-CSRF-Token, x-xsrf-token, XSRF-TOKEN'
exposed-headers: 'Authorization,Link,X-Total-Count,XSRF-TOKEN, X-XSRF-TOKEN'
allow-credentials: true
max-age: 86400
wrote an interceptor
#Injectable()
export class HttpXSRFInterceptor implements HttpInterceptor {
constructor(private tokenExtractor: HttpXsrfTokenExtractor, private csrfService:CSRFService, private $sessionStorage: SessionStorageService) {
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const headerName = 'XSRF-TOKEN';
const respHeaderName = 'X-XSRF-TOKEN';
let token = this.tokenExtractor.getToken() as string;
if (token !== null && !req.headers.has(headerName)) {
req = req.clone({ headers: req.headers.set(respHeaderName, token) });
req.clone({
withCredentials: true
});
}
return next.handle(req);
}
}
added HttpClientXsrfModule in my app.module.ts and the interceptor
HttpClientXsrfModule.withOptions({
cookieName: 'XSRF-TOKEN',
headerName: 'X-XSRF-TOKEN',
}),
{
provide: HTTP_INTERCEPTORS,
useClass: HttpXSRFInterceptor,
multi: true
},
My Problem:
I dont get a xsrf token when starting my App in the browser, but after I send a post request the token gets set as a cookie.
For example when logging in, the first attempt fails due to the missing token, but the second login request is successful because now the response header for the xsrf token is not null anymore. Furthermore, the token does not update itself even though the server response has a new token in its header.
From my understanding
the first time I get my token should be immediately after loading the start page of my app
the token should be updated after each response from the server (backend) and the updated token is used for the next request
Therefore my problem is that both these issues do not happen and I don't know how to fix it.
I appreciate any help!
cheers
I'm the author of Ionic for JHipster so hopefully, I can help you with this.
First of all, CSRF shouldn't be an issue unless you're running your apps on the same port. In my experience, when you run them on separate ports, your client can't read the cookie. As for CORS, that's not a problem for me when running locally. I believe it's because the CORS settings for the dev profile are wide open. Can you try using the settings from the dev profile in your prod profile and see if it helps?
For reference, they are:
jhipster:
cors:
allowed-origins: '*'
allowed-methods: '*'
allowed-headers: '*'
exposed-headers: 'Authorization,Link,X-Total-Count'
allow-credentials: true
max-age: 1800
If this works, I'd try changing your allowed origins to an array, or just use one. http://localhost:8100 should be all you need if running locally.
I started to work with keycloak, and here is a setup I want to test.
I want to test this scenario:
It works, but I want to implement role-based access to apps behind Nginx proxy and I can't understand how exactly payload of jwt token generates.
Here is my JWT tokens payload:
"jti": "f5f07b6f-ccae-4f57-a8ea-ae02ebb3cb12",
"exp": 1569263630,
"nbf": 0,
"iat": 1569227630,
"iss": "https://keycloak.domain.local/auth/realms/LDAP_test",
"sub": "fedc6baf-4ba4-4fa6-924c-9501edf070f7",
"typ": "Serialized-ID",
"auth_time": 0,
"session_state": "aa0052ee-b5e1-45cc-bee4-e7bccdfa4a59",
"state_checker": "sC_nvlDXfjUDHhC15ZDpPauX5JkxhvVtYUOn62PhtV8"
I want my token to contain roles, username and email and i run out of ideas how to put it there. Is client somehow related to the content of the token or keycloak always gives everything he have into it?
Here are my Nginx client settings:
server {
server_name demo-a.domain.local;
location / {
proxy_pass http://10.10.10.168/index.html;
access_by_lua '
local opts = {
redirect_uri_path = "/redirect_uri",
accept_none_alg = true,
discovery = "https:/keycloak.domain.local/auth/realms/LDAP_test/.well-known/openid-configuration",
client_id = "nginx-gateway",
client_secret = "19fe43bc-4167-4433-816a-eb96da33f9a3",
redirect_uri_scheme = "https",
logout_path = "/logout",
redirect_after_logout_uri = "https://keycloak.domain.local/auth/realms/LDAP_test/protocol/openid-connect/logout?redirect_uri=https://www.nginx-gateway.domain.local/",
redirect_after_logout_with_id_token_hint = false,
session_contents = {id_token=true}
}
-- call introspect for OAuth 2.0 Bearer Access Token validation
local res, err = require("resty.openidc").authenticate(opts)
if err then
ngx.status = 403
ngx.say(err)
ngx.exit(ngx.HTTP_FORBIDDEN)
end
';
}
Thanks in advance!
Did you configure the client to be openid client? Is it UI or machine client?
To cause your JWT to have roles, one follows usual below flow:
Create client in Keycloak admin-console and configure it to support
open-id-connect.
Add users to Keycloak.
Assign roles to users.
Configure your client to have the client id of Keycloak client.
Exercise browser or machine flow which involves passing of JWT to the
client. JWT will contain the roles as claims.
Still debugging it, but here are a few pointers you will definitely need....
You need to be Authenticating Reverse Proxy with KeyCloak while using the keycloak behind an nginx proxy
Ok, completely forget about this question, but still here is a solution.
Just use a proper oidc proxy like gatekeeper (louketo now) or oauth2-proxy.
I have a thick client application (C# but that should not matter).
All the users already exist in an authentication/authorization (3rd party) system that provides OAuth 2 API (authorize/access_token plus a user_info service).
I have a Spring Boot web service tier that will have RESTful web services that will be called by the thick client application that must only be called by authenticated users for protected web services.
To authenticate the thick client will launch a Web Browser (OS installed default) and will open https to restful.web.server:8443 /login of the Spring Boot web service tier. This will do the OAuth 2 (authorization_code) interaction. Once redirected back with a valid token I want to redirect to a custom URI passing the token and for the browser to close (if possible) so an OS registered application can extract the token and pass it via an IPC mechanism to the thick client application.
The thick client application can then pass the token to the Web Services in the header (Authorize: TOKEN_TYPE TOKEN_VALUE).
The Web Services must then validate the authenticity of the token.
The Web Services if called with an invalid token must just return an HTTP error and JSON error content (e.g. code+message) and not try and redirect to the login screen. This will be orchestrated by the thick client application.
I have no concern with any of the custom URI handling, IPC development, or thick client web service calls. It is all the Spring/SSO magic in getting the token to be sent to my thick client and returning the relevant error from protected web services without returning a redirect to the SSO login.
I appear to be authenticating and being sent a token but then I get an exception.
I have made some progress and it appears that by manually launching a browser and hitting my web service tier https to restful.web.server:8443 /login it redirects to the SSO site https to 3rdparty.sso.server /oauth/authorization (passing in client_id, redirect_uri, response_type=code, state). I can log in, and Spring is calling the https to 3rdparty.sso.server /oauth/access_token endpoint (I had to create a custom RequestEnhancer to add in Authorization: Basic ENCODED_CLIENT_ID_AND_CLIENT_SECRET to satisfy the access_token SSO API requirement).
This returns 200 OK but then I get exceptions and do not know how to extract the token. The access_token returned may not be using the standard property names but unsure when to go and check if this is the case. I done the authentication this way to keep the client id and client secret out of the thick client application and my web services must do the authorisation anyway. If there is a better way or pointers to someone else doing this already it would be greatly appreciated. I find so many examples that are either not quite relevant or more towards web applications.
server:
port: 8443
ssl:
key-store: classpath:keystore.p12
key-store-password: **********
keyStoreType: PKCS12
keyAlias: tomcat
servlet:
context-path: /
session:
cookie:
name: UISESSION
security:
basic:
enabled: false
oauth2:
client:
clientId: *******
clientSecret: *****************
accessTokenUri: https://3rdparty.sso.server/oauth2/access_token
userAuthorizationUri: https://3rdparty.sso.server/oauth2/authorize
authorizedGrantTypes: authorization_code,refresh_token
scope:
tokenName: accessToken
redirectUri: https://restful.web.server:8443/login
authenticationScheme: query
clientAuthenticationScheme: header
resource:
userInfoUri: https://3rdparty.sso.server/oauth2/userinfo
logging:
level:
org:
springframework: DEBUG
spring:
http:
logRequestDetails: true
logResponseDetails: true
#Configuration
#EnableOAuth2Sso
#Order(value=0)
public class ServiceConectWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// From the root '/' down...
.antMatcher("/**")
// requests are authorised...
.authorizeRequests()
// ...to these url's...
.antMatchers("/", "/login**", "/debug/**", "/webjars/**", "/error**")
// ...without security being applied...
.permitAll()
// ...any other requests...
.anyRequest()
// ...the user must be authenticated.
.authenticated()
.and()
.formLogin().disable()
.logout()
.logoutSuccessUrl("/login")
.permitAll()
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
;
}
I expect that the secured web services would be accessible once authenticated via the browser whilst testing without the client and would not expect exceptions to be thrown. I need to be able to extract the returned token and pass it back to my thick client.
Redirects to 'https://3rdparty.sso.server/oauth2/authorize?client_id=***HIDDEN_CLIENT_ID***&redirect_uri=https://localhost:8443/login&response_type=code&state=***HIDDEN_STATE_1***'
Then FilterChainProxy : /login?code=***HIDDEN_CODE_1***&state=***HIDDEN_STATE_1*** at position 6 of 12 in additional filter chain;
Request is to process authentication
RestTemplate : HTTP POST https://3rdparty.sso.server/oauth2/access_token
RestTemplate : Response 200 OK
IllegalStateException: Access token provider returned a null access token, which is illegal according to the contract.
at OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:223) ```
Then end up at an error page
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
There was an unexpected error (type=Internal Server Error, status=500).
Access token provider returned a null access token, which is illegal according to the contract.
The access_token service was returning non-standard JSON names.
I created a MyOwnOAuth2AccessToken with the relevant non-standard JSON names the necessary de/serialisation classes.
I created a MyOauth2AccesTokenHttpMessageConverter class for returning my OAuth2AccessToken.
The MyOauth2AccesTokenHttpMessageConverter was plumbed in from an
#Configuration
public class ServiceConnectUserInfoRestTemplateFactory implements UserInfoRestTemplateFactory
within the
#Bean
#Override
public OAuth2RestTemplate getUserInfoRestTemplate()
method with the following code:
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
messageConverters.add(new ItisOAuth2AccessTokenHttpMessageConverter());
messageConverters.addAll((new RestTemplate()).getMessageConverters());
accessTokenProvider.setMessageConverters(messageConverters);
There is probably a better way to do this but this worked for me.