Problem
CORS issue in the browser, when app is trying to reach Keycloak
Cross-Origin Request Blocked
Recommendations
These recommendations are based on real experience. I’m sure there can be also another success path for CORS issues, but you will need more deep knowledge (see recommended doc section), so I would recommend to stick with these basic rules:
-
Configure
Web Origins
of used OIDC client in the Keycloak correctlyThat’s
Origin
(first part of «URL»), which is calling Keycloak — usually «URL» of some SPA (Vue, React, Angular, JS) application/web site.
What isOrigin
? — https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/OriginOrigin: <scheme> «://» <hostname> [ «:» <port> ]
So
https://domain.com/*
is not a valid origin (it should be onlyhttps://domain.com
).
Examples of valid originshttps://1.1.1.1:8443,https://domain.com,http://domain.com:8888
.
Examples of invalid originshttps://1.1.1.1:8443/,https://domain.com/path,http://domain.com:8888/*
. -
Don’t use any magic «regexp», e.g.
*,+
in the Origin OIDC Keycloak client configuration, be explicithttps://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
In requests with credentials, it is treated as the literal header name «*» without special semantics. Note that the Authorization header can’t be wildcarded and always needs to be listed explicitly.
-
Full Scope must be disabled
Make sure
Full Scope Allowed
is notON
in the Keycloak clientScope Mapping
configuration -
Don’t use
localhost
and local file system for the developmentIt is not saying not using local machine, just don’t use
localhost
in the browser. Local machine has also local IP of your network, e.g.192.168.0.133
, so I would use it everywhere (in the browser, in the OIDC client Origin configuration, ….). All SPA dev servers can bind also your local IP (e.g. use binding interace0.0.0.0
), so they can run also on local IP, not just onlocalhost
or127.0.0.1
interface. You can use local hosts file and develop app on fake local «domain» directly. Problem is that some browser may have very likely own config forlocalhost
— https://stackoverflow.com/questions/10883211/deadly-cors-when-http-localhost-is-the-originLocal file system (URL in the browser which refers to your local file system, e.g.
file:///C:/Users/User/Desktop/index.html
) don’t provide origin, so they have errorOrigin null is not allowed by Access-Control-Allow-Origin.
, which is valid. Keycloak can’t allownull
origin. Use proper webserver, so you have URL withhttps
(http
) protocol. -
Use browser network console to inspect prefligh request
Preflight request (
OPTIONS
method) is saying to Keycloak «what» is requested by the app and then Keycloak returns «what» is allowed actually. Simplecurl
debugging on Linux of preflight request — app is developed onhttp://192.168.0.133:8080
, so that’s used as an origin:$ curl -X OPTIONS -H "Origin: http://192.168.0.133:8080" -H "Access-Control-Request-Method: POST" -H "Access-Control-Request-Headers: authorization,x-requested-with" -k https://<keycloak-host>/auth/realms/<realm>/protocol/openid-connect/token --silent --verbose 2>&1 | grep Access-Control > Access-Control-Request-Method: POST > Access-Control-Request-Headers: authorization,x-requested-with < Access-Control-Allow-Headers: Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers, Authorization < Access-Control-Allow-Origin: http://192.168.0.133:8080 < Access-Control-Allow-Credentials: true < Access-Control-Allow-Methods: POST, OPTIONS < Access-Control-Max-Age: 3600
Requested details from pre preflight request e.g.
Origin, Access-Control-Request-Method, Access-Control-Request-Headers
must be included in the preflight response e.g.Access-Control-Allow-Origin, Access-Control-Request-Method, Access-Control-Request-Headers
. If they are not or they are not matching, then browser will denies to execute request. This is example of preflight request for token endpoint, but each Keycloak URL may have own preflight request, which should be inspected. -
Check Keycloak response body
Keycloak response body may contains a clue. For example
Client secret not provided in request
, when private OIDC client was used in the PKCE authentication instead of public client. -
Use correct OIDC flow
So for SPA it is
Authorization Code Flow with Proof Key for Code Exchange (PKCE)
usually (so used OIDC client must be public). I can imagine alsoClient Credentials Flow
orDirect Access Grant Flow
can be used.Implicit Flow
is deprecated flow, so don’t use it anymore.
I would recommend to read https://developer.okta.com/docs/concepts/oauth-openid/#what-kind-of-client-are-you-buildingCORS issue to auth endpoint e.g.
https://<keycloak-host>/auth/realms/<realm>/protocol/openid-connect/auth
indicates wrong backend/API implementation or used flow. XHR request shouldn’t never reach auth Keycloak endpoint (that is designated only for full user browser access and not XHR access) -
Use certified OIDC library
Unless, you are using simple flows — but mature library may make your life easy also in their case.
See: https://openid.net/developers/certified/ OIDC is a standard SSO standard. You really don’t need to use any library withkeycloak
in the name. But used library must support OIDC SSO protocol. Actually, I would avoid anykeycloak
libraries completely. It may introduces vendor dependency and you will need to change the code in the future, when you will decide to change used Identity provider. Humble recommendation for Angular: https://github.com/damienbod/angular-auth-oidc-client
Example implementations
- React: https://github.com/dasniko/keycloak-reactjs-demo
- Angular: https://github.com/damienbod/angular-auth-oidc-client/tree/main/projects
Recommended doc
- https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors
Please don’t contact me
These recommendations may help you. If not please don’t contact me with the request for the help (unless you demand commercial paid support). Unfortunately, I don’t have a capacity for that. Use another resources, e.g. https://keycloak.discourse.group/ and always mention how did you configure client and how did you debug the issue (preflight request can be very usefull to see what is requested and what is returned by Keycloak). Requests I have CORS issue, so what to do
are useless. Provides details and then you will increase your chance that someone gives you some clue. Example of usefull problem report (it’s clear which Keycloak version is used, how is OIDC client configured, how SPA auth code looks like, which library is used, what is the CORS issue):
1.) I use Keycloak 12.0.3 and OIDC client with this configuration (exported json model):
{
"clientId": "vue",
"description": "My test OIDC client for my Vue SPA app",
"surrogateAuthRequired": false,
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": [
"https://192.168.11.133/*"
],
"webOrigins": [
"https://192.168.11.133/*"
],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
"standardFlowEnabled": true,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": true,
"serviceAccountsEnabled": false,
"publicClient": false,
"frontchannelLogout": false,
"protocol": "openid-connect",
"attributes": {
"saml.assertion.signature": "false",
"saml.multivalued.roles": "false",
"saml.force.post.binding": "false",
"saml.encrypt": "false",
"backchannel.logout.revoke.offline.tokens": "false",
"saml.server.signature": "false",
"saml.server.signature.keyinfo.ext": "false",
"exclude.session.state.from.auth.response": "false",
"backchannel.logout.session.required": "true",
"client_credentials.use_refresh_token": "false",
"saml_force_name_id_format": "false",
"saml.client.signature": "false",
"tls.client.certificate.bound.access.tokens": "false",
"saml.authnstatement": "false",
"display.on.consent.screen": "false",
"saml.onetimeuse.condition": "false"
},
"authenticationFlowBindingOverrides": {},
"fullScopeAllowed": true,
"nodeReRegistrationTimeout": -1,
"defaultClientScopes": [
"web-origins",
"role_list",
"profile",
"roles",
"email"
],
"optionalClientScopes": [
"address",
"phone",
"offline_access",
"microprofile-jwt"
],
"access": {
"view": true,
"configure": true,
"manage": true
}
}
2.) Snippet of my authentication Vue code — I use keycloak-js
library version 9.0.0:
const initOptions = {
url: process.env.VUE_APP_KEYCLOAK_OPTIONS_URL,
realm: process.env.VUE_APP_KEYCLOAK_OPTIONS_REALM,
clientId: process.env.VUE_APP_KEYCLOAK_OPTIONS_CLIENTID,
onLoad: process.env.VUE_APP_KEYCLOAK_OPTIONS_ONLOAD,
}
const keycloak = Keycloak(initOptions)
keycloak.init(
{ onLoad: initOptions.onLoad, promiseType: 'native', checkLoginIframe: false }
).then(async authenticated => {
if (!authenticated) {
window.location.reload()
return
} else {
Vue.prototype.$keycloak = keycloak
await store.dispatch('user/keycloakLogin', keycloak.token)
}
setInterval(() => {
keycloak.updateToken(70).then((refreshed) => {
if (refreshed) {
console.log('Token refreshed')
setToken(keycloak.token)
}
}).catch(error => {
store.commit("snackbar/setSnack", {message:'Your access token was not refreshed automatically. Try full page reload in your browser. Error: ' + error, show: true});
console.log('Failed to refresh token', error)
})
}, 60000)
}).catch(error => {
console.log('Authentication failed: ', error)
})
Env variables:
VUE_APP_KEYCLOAK_OPTIONS_URL='https://192.169.11.133:8443/auth'
VUE_APP_KEYCLOAK_OPTIONS_REALM='master'
VUE_APP_KEYCLOAK_OPTIONS_CLIENTID='vue'
VUE_APP_KEYCLOAK_OPTIONS_ONLOAD='login-required'
3.) Recorded requests of the app with the Keycloak with recoded CORS issue (preflight request included) is available in the har file.
<attach har file — see how to generate har file>
You can use this report as an exercise and find obvious problem(s), which violate recommendations above.
1. Overview
Keycloak supports both OpenID Connect (an extension to OAuth 2.0) and SAML 2.0. When securing clients and services the first thing you need to
decide is which of the two you are going to use. If you want you can also choose to secure some with OpenID Connect and others with SAML.
To secure clients and services you are also going to need an adapter or library for the protocol you’ve selected. Keycloak comes with its own
adapters for selected platforms, but it is also possible to use generic OpenID Connect Relying Party and SAML Service Provider libraries.
1.1. What are Client Adapters?
Keycloak client adapters are libraries that make it very easy to secure applications and services with Keycloak. We call them
adapters rather than libraries as they provide a tight integration to the underlying platform and framework. This makes our adapters easy to use and they
require less boilerplate code than what is typically required by a library.
1.2. Supported Platforms
1.2.1. OpenID Connect
Java
-
JBoss EAP
-
WildFly
-
Fuse
-
Tomcat
-
Jetty 9
-
Servlet Filter
-
Spring Boot
-
Spring Security
JavaScript (client-side)
-
JavaScript
Node.js (server-side)
-
Node.js
1.2.2. SAML
Apache HTTP Server
-
mod_auth_mellon
1.3. Supported Protocols
1.3.1. OpenID Connect
OpenID Connect (OIDC) is an authentication protocol that is an extension of OAuth 2.0.
While OAuth 2.0 is only a framework for building authorization protocols and is mainly incomplete, OIDC is a full-fledged authentication and authorization
protocol. OIDC also makes heavy use of the Json Web Token (JWT) set of standards. These standards define an
identity token JSON format and ways to digitally sign and encrypt that data in a compact and web-friendly way.
There are really two types of use cases when using OIDC. The first is an application that asks the Keycloak server to authenticate
a user for them. After a successful login, the application will receive an identity token and an access token. The identity token
contains information about the user such as username, email, and other profile information. The access token is digitally signed by
the realm and contains access information (like user role mappings) that the application can use to determine what resources the user
is allowed to access on the application.
The second type of use cases is that of a client that wants to gain access to remote services. In this case, the client asks Keycloak
to obtain an access token it can use to invoke on other remote services on behalf of the user. Keycloak authenticates the user
then asks the user for consent to grant access to the client requesting it. The client then receives the access token. This access token
is digitally signed by the realm. The client can make REST invocations on remote services using this access token. The REST service
extracts the access token, verifies the signature of the token, then decides based on access information within the token whether or not to process
the request.
1.3.2. SAML 2.0
SAML 2.0 is a similar specification to OIDC but a lot older and more mature. It has its roots in SOAP and the plethora
of WS-* specifications so it tends to be a bit more verbose than OIDC. SAML 2.0 is primarily an authentication protocol
that works by exchanging XML documents between the authentication server and the application. XML signatures and encryption are used to verify requests and responses.
In Keycloak SAML serves two types of use cases: browser applications and REST invocations.
There are really two types of use cases when using SAML. The first is an application that asks the Keycloak server to authenticate
a user for them. After a successful login, the application will receive an XML document that contains
something called a SAML assertion that specifies various attributes about the user. This XML document is digitally signed by
the realm and contains access information (like user role mappings) that the application can use to determine what resources the user
is allowed to access on the application.
The second type of use cases is that of a client that wants to gain access to remote services. In this case, the client asks Keycloak
to obtain a SAML assertion it can use to invoke on other remote services on behalf of the user.
1.3.3. OpenID Connect vs. SAML
Choosing between OpenID Connect and SAML is not just a matter of using a newer protocol (OIDC) instead of the older more mature protocol (SAML).
In most cases Keycloak recommends using OIDC.
SAML tends to be a bit more verbose than OIDC.
Beyond verbosity of exchanged data, if you compare the specifications you’ll find that OIDC was designed to work with the web while SAML was retrofitted to work on top of the web. For example, OIDC is also more suited for HTML5/JavaScript applications because it is
easier to implement on the client side than SAML. As tokens are in the JSON format,
they are easier to consume by JavaScript. You will also find several nice features that
make implementing security in your web applications easier. For example, check out the iframe trick that the specification uses to easily determine if a user is still logged in or not.
SAML has its uses though. As you see the OIDC specifications evolve you see they implement more and more features that SAML has had for years. What we often see is that people pick SAML over OIDC because of the perception that it is more mature and also because they already have existing applications that are secured with it.
2. OpenID Connect
This section describes how you can secure applications and services with OpenID Connect using either Keycloak adapters or generic OpenID Connect
Relying Party libraries.
2.1. Java Adapters
Keycloak comes with a range of different adapters for Java application. Selecting the correct adapter depends on the target platform.
All Java adapters share a set of common configuration options described in the Java Adapters Config chapter.
2.1.1. Java Adapter Config
Each Java adapter supported by Keycloak can be configured by a simple JSON file.
This is what one might look like:
{
"realm" : "demo",
"resource" : "customer-portal",
"realm-public-key" : "MIGfMA0GCSqGSIb3D...31LwIDAQAB",
"auth-server-url" : "https://localhost:8443/auth",
"ssl-required" : "external",
"use-resource-role-mappings" : false,
"enable-cors" : true,
"cors-max-age" : 1000,
"cors-allowed-methods" : "POST, PUT, DELETE, GET",
"cors-exposed-headers" : "WWW-Authenticate, My-custom-exposed-Header",
"bearer-only" : false,
"enable-basic-auth" : false,
"expose-token" : true,
"verify-token-audience" : true,
"credentials" : {
"secret" : "234234-234234-234234"
},
"connection-pool-size" : 20,
"disable-trust-manager": false,
"allow-any-hostname" : false,
"truststore" : "path/to/truststore.jks",
"truststore-password" : "geheim",
"client-keystore" : "path/to/client-keystore.jks",
"client-keystore-password" : "geheim",
"client-key-password" : "geheim",
"token-minimum-time-to-live" : 10,
"min-time-between-jwks-requests" : 10,
"public-key-cache-ttl": 86400,
"redirect-rewrite-rules" : {
"^/wsmaster/api/(.*)$" : "/api/$1"
}
}
You can use ${…}
enclosure for system property replacement. For example ${jboss.server.config.dir}
would be replaced by /path/to/Keycloak
.
Replacement of environment variables is also supported via the env
prefix, e.g. ${env.MY_ENVIRONMENT_VARIABLE}
.
The initial config file can be obtained from the admin console. This can be done by opening the admin console, select Clients
from the menu and clicking
on the corresponding client. Once the page for the client is opened click on the Installation
tab and select Keycloak OIDC JSON
.
Here is a description of each configuration option:
- realm
-
Name of the realm.
This is REQUIRED. - resource
-
The client-id of the application. Each application has a client-id that is used to identify the application.
This is REQUIRED. - realm-public-key
-
PEM format of the realm public key. You can obtain this from the administration console.
This is OPTIONAL and it’s not recommended to set it. If not set, the adapter will download this from Keycloak and
it will always re-download it when needed (eg. Keycloak rotate it’s keys). However if realm-public-key is set, then adapter
will never download new keys from Keycloak, so when Keycloak rotate it’s keys, adapter will break. - auth-server-url
-
The base URL of the Keycloak server. All other Keycloak pages and REST service endpoints are derived from this. It is usually of the form
https://host:port/auth
.
This is REQUIRED. - ssl-required
-
Ensures that all communication to and from the Keycloak server is over HTTPS.
In production this should be set toall
.
This is OPTIONAL.
The default value is external meaning that HTTPS is required by default for external requests.
Valid values are ‘all’, ‘external’ and ‘none’. - confidential-port
-
The confidential port used by the Keycloak server for secure connections over SSL/TLS.
This is OPTIONAL.
The default value is 8443. - use-resource-role-mappings
-
If set to true, the adapter will look inside the token for application level role mappings for the user. If false, it will look at the realm level for user role mappings.
This is OPTIONAL.
The default value is false. - public-client
-
If set to true, the adapter will not send credentials for the client to Keycloak.
This is OPTIONAL.
The default value is false. - enable-cors
-
This enables CORS support. It will handle CORS preflight requests. It will also look into the access token to determine valid origins.
This is OPTIONAL.
The default value is false. - cors-max-age
-
If CORS is enabled, this sets the value of the
Access-Control-Max-Age
header.
This is OPTIONAL.
If not set, this header is not returned in CORS responses. - cors-allowed-methods
-
If CORS is enabled, this sets the value of the
Access-Control-Allow-Methods
header.
This should be a comma-separated string.
This is OPTIONAL.
If not set, this header is not returned in CORS responses. - cors-allowed-headers
-
If CORS is enabled, this sets the value of the
Access-Control-Allow-Headers
header.
This should be a comma-separated string.
This is OPTIONAL.
If not set, this header is not returned in CORS responses. - cors-exposed-headers
-
If CORS is enabled, this sets the value of the
Access-Control-Expose-Headers
header.
This should be a comma-separated string.
This is OPTIONAL.
If not set, this header is not returned in CORS responses. - bearer-only
-
This should be set to true for services. If enabled the adapter will not attempt to authenticate users, but only verify bearer tokens.
This is OPTIONAL.
The default value is false. - autodetect-bearer-only
-
This should be set to true if your application serves both a web application and web services (e.g. SOAP or REST).
It allows you to redirect unauthenticated users of the web application to the Keycloak login page,
but send an HTTP401
status code to unauthenticated SOAP or REST clients instead as they would not understand a redirect to the login page.
Keycloak auto-detects SOAP or REST clients based on typical headers likeX-Requested-With
,SOAPAction
orAccept
.
The default value is false. - enable-basic-auth
-
This tells the adapter to also support basic authentication. If this option is enabled, then secret must also be provided.
This is OPTIONAL.
The default value is false. - expose-token
-
If
true
, an authenticated browser client (via a JavaScript HTTP invocation) can obtain the signed access token via the URLroot/k_query_bearer_token
.
This is OPTIONAL.
The default value is false. - credentials
-
Specify the credentials of the application. This is an object notation where the key is the credential type and the value is the value of the credential type.
Currently password and jwt is supported. This is REQUIRED only for clients with ‘Confidential’ access type. - connection-pool-size
-
This config option defines how many connections to the Keycloak server should be pooled.
This is OPTIONAL.
The default value is20
. - disable-trust-manager
-
If the Keycloak server requires HTTPS and this config option is set to
true
you do not have to specify a truststore.
This setting should only be used during development and never in production as it will disable verification of SSL certificates.
This is OPTIONAL.
The default value isfalse
. - allow-any-hostname
-
If the Keycloak server requires HTTPS and this config option is set to
true
the Keycloak server’s certificate is validated via the truststore,
but host name validation is not done.
This setting should only be used during development and never in production as it will disable verification of SSL certificates.
This seting may be useful in test environments This is OPTIONAL.
The default value isfalse
. - proxy-url
-
The URL for the HTTP proxy if one is used.
- truststore
-
The value is the file path to a truststore file.
If you prefix the path withclasspath:
, then the truststore will be obtained from the deployment’s classpath instead.
Used for outgoing HTTPS communications to the Keycloak server.
Client making HTTPS requests need a way to verify the host of the server they are talking to.
This is what the trustore does.
The keystore contains one or more trusted host certificates or certificate authorities.
You can create this truststore by extracting the public certificate of the Keycloak server’s SSL keystore.
This is REQUIRED unlessssl-required
isnone
ordisable-trust-manager
istrue
. - truststore-password
-
Password for the truststore.
This is REQUIRED iftruststore
is set and the truststore requires a password. - client-keystore
-
This is the file path to a keystore file.
This keystore contains client certificate for two-way SSL when the adapter makes HTTPS requests to the Keycloak server.
This is OPTIONAL. - client-keystore-password
-
Password for the client keystore.
This is REQUIRED ifclient-keystore
is set. - client-key-password
-
Password for the client’s key.
This is REQUIRED ifclient-keystore
is set. - always-refresh-token
-
If true, the adapter will refresh token in every request.
Warning — when enabled this will result in a request to Keycloak for every request to your application. - register-node-at-startup
-
If true, then adapter will send registration request to Keycloak.
It’s false by default and useful only when application is clustered.
See Application Clustering for details - register-node-period
-
Period for re-registration adapter to Keycloak.
Useful when application is clustered.
See Application Clustering for details - token-store
-
Possible values are session and cookie.
Default is session, which means that adapter stores account info in HTTP Session.
Alternative cookie means storage of info in cookie.
See Application Clustering for details - token-cookie-path
-
When using a cookie store, this option sets the path of the cookie used to store account info. If it’s a relative path,
then it is assumed that the application is running in a context root, and is interpreted relative to that context root.
If it’s an absolute path, then the absolute path is used to set the cookie path. Defaults to use paths relative to the context root. - principal-attribute
-
OpenID Connect ID Token attribute to populate the UserPrincipal name with.
If token attribute is null, defaults tosub
.
Possible values aresub
,preferred_username
,email
,name
,nickname
,given_name
,family_name
. - turn-off-change-session-id-on-login
-
The session id is changed by default on a successful login on some platforms to plug a security attack vector. Change this to true if you want to turn this off This is OPTIONAL.
The default value is false. - token-minimum-time-to-live
-
Amount of time, in seconds, to preemptively refresh an active access token with the Keycloak server before it expires.
This is especially useful when the access token is sent to another REST client where it could expire before being evaluated.
This value should never exceed the realm’s access token lifespan.
This is OPTIONAL. The default value is0
seconds, so adapter will refresh access token just if it’s expired. - min-time-between-jwks-requests
-
Amount of time, in seconds, specifying minimum interval between two requests to Keycloak to retrieve new public keys.
It is 10 seconds by default.
Adapter will always try to download new public key when it recognize token with unknownkid
. However it won’t try it more
than once per 10 seconds (by default). This is to avoid DoS when attacker sends lots of tokens with badkid
forcing adapter
to send lots of requests to Keycloak. - public-key-cache-ttl
-
Amount of time, in seconds, specifying maximum interval between two requests to Keycloak to retrieve new public keys.
It is 86400 seconds (1 day) by default.
Adapter will always try to download new public key when it recognize token with unknownkid
. If it recognize token with knownkid
, it will
just use the public key downloaded previously. However at least once per this configured interval (1 day by default) will be new
public key always downloaded even if thekid
of token is already known. - ignore-oauth-query-parameter
-
Defaults to
false
, if set totrue
will turn off processing of theaccess_token
query parameter for bearer token processing. Users will not be able to authenticate
if they only pass in anaccess_token
- redirect-rewrite-rules
-
If needed, specify the Redirect URI rewrite rule. This is an object notation where the key is the regular expression to which the Redirect URI is to be matched and the value is the replacement String.
$
character can be used for backreferences in the replacement String. - verify-token-audience
-
If set to
true
, then during authentication with the bearer token, the adapter will verify whether the token contains this
client name (resource) as an audience. The option is especially useful for services, which primarily serve requests authenticated
by the bearer token. This is set tofalse
by default, however for improved security, it is recommended to enable this.
See Audience Support for more details about audience support.
2.1.2. JBoss EAP/WildFly Adapter
To be able to secure WAR apps deployed on JBoss EAP, WildFly or JBoss AS, you must install and configure the
Keycloak adapter subsystem. You then have two options to secure your WARs.
You can provide an adapter config file in your WAR and change the auth-method to KEYCLOAK within web.xml.
Alternatively, you don’t have to modify your WAR at all and you can secure it via the Keycloak adapter subsystem configuration in the configuration file, such as standalone.xml
.
Both methods are described in this section.
Installing the adapter
Adapters are available as a separate archive depending on what server version you are using.
We only test and maintain adapter with the most recent version of WildFly available upon the release. Once new version of WildFly is released, the current adapters become deprecated and support for them will be removed after next WildFly release. The other alternative is to switch your applications from WildFly to the JBoss EAP, as the JBoss EAP adapter is supported for much longer period. |
Install on WildFly 9 or newer:
$ cd $WILDFLY_HOME
$ unzip keycloak-wildfly-adapter-dist-11.0.3.zip
Install on JBoss EAP 7:
$ cd $EAP_HOME
$ unzip keycloak-eap7-adapter-dist-11.0.3.zip
Install on JBoss EAP 6:
$ cd $EAP_HOME
$ unzip keycloak-eap6-adapter-dist-11.0.3.zip
Install on JBoss AS 7.1:
$ cd $JBOSS_HOME
$ unzip keycloak-as7-adapter-dist-11.0.3.zip
This ZIP archive contains JBoss Modules specific to the Keycloak adapter. It also contains JBoss CLI scripts to configure the adapter subsystem.
To configure the adapter subsystem if the server is not running execute:
Alternatively, you can specify the server.config property while installing adapters from the command line to install adapters using a different config, for example: -Dserver.config=standalone-ha.xml .
|
WildFly 11 or newer
$ ./bin/jboss-cli.sh --file=bin/adapter-elytron-install-offline.cli
WildFly 10 or older
$ ./bin/jboss-cli.sh --file=bin/adapter-install-offline.cli
It is possible to use the legacy non-Elytron adapter on WildFly 11 or newer as well, meaning you can use adapter-install-offline.cli even on those versions. However, we recommend to use the newer Elytron adapter. |
Alternatively, if the server is running execute:
WildFly 11 or newer
$ ./bin/jboss-cli.sh -c --file=bin/adapter-elytron-install.cli
WildFly 10 or older
$ ./bin/jboss-cli.sh -c --file=bin/adapter-install.cli
JBoss SSO
WildFly has built-in support for single sign-on for web applications deployed to the same WildFly
instance. This should not be enabled when using Keycloak.
Required Per WAR Configuration
This section describes how to secure a WAR directly by adding configuration and editing files within your WAR package.
The first thing you must do is create a keycloak.json
adapter configuration file within the WEB-INF
directory of your WAR.
The format of this configuration file is described in the Java adapter configuration section.
Next you must set the auth-method
to KEYCLOAK
in web.xml
.
You also have to use standard servlet security to specify role-base constraints on your URLs.
Here’s an example:
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<module-name>application</module-name>
<security-constraint>
<web-resource-collection>
<web-resource-name>Admins</web-resource-name>
<url-pattern>/admin/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>Customers</web-resource-name>
<url-pattern>/customers/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>user</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<login-config>
<auth-method>KEYCLOAK</auth-method>
<realm-name>this is ignored currently</realm-name>
</login-config>
<security-role>
<role-name>admin</role-name>
</security-role>
<security-role>
<role-name>user</role-name>
</security-role>
</web-app>
Securing WARs via Adapter Subsystem
You do not have to modify your WAR to secure it with Keycloak. Instead you can externally secure it via the Keycloak Adapter Subsystem.
While you don’t have to specify KEYCLOAK as an auth-method
, you still have to define the security-constraints
in web.xml
.
You do not, however, have to create a WEB-INF/keycloak.json
file.
This metadata is instead defined within server configuration (i.e. standalone.xml
) in the Keycloak subsystem definition.
<extensions>
<extension module="org.keycloak.keycloak-adapter-subsystem"/>
</extensions>
<profile>
<subsystem xmlns="urn:jboss:domain:keycloak:1.1">
<secure-deployment name="WAR MODULE NAME.war">
<realm>demo</realm>
<auth-server-url>http://localhost:8081/auth</auth-server-url>
<ssl-required>external</ssl-required>
<resource>customer-portal</resource>
<credential name="secret">password</credential>
</secure-deployment>
</subsystem>
</profile>
The secure-deployment
name
attribute identifies the WAR you want to secure.
Its value is the module-name
defined in web.xml
with .war
appended. The rest of the configuration corresponds pretty much one to one with the keycloak.json
configuration options defined in Java adapter configuration.
The exception is the credential
element.
To make it easier for you, you can go to the Keycloak Administration Console and go to the Client/Installation tab of the application this WAR is aligned with.
It provides an example XML file you can cut and paste.
If you have multiple deployments secured by the same realm you can share the realm configuration in a separate element. For example:
<subsystem xmlns="urn:jboss:domain:keycloak:1.1">
<realm name="demo">
<auth-server-url>http://localhost:8080/auth</auth-server-url>
<ssl-required>external</ssl-required>
</realm>
<secure-deployment name="customer-portal.war">
<realm>demo</realm>
<resource>customer-portal</resource>
<credential name="secret">password</credential>
</secure-deployment>
<secure-deployment name="product-portal.war">
<realm>demo</realm>
<resource>product-portal</resource>
<credential name="secret">password</credential>
</secure-deployment>
<secure-deployment name="database.war">
<realm>demo</realm>
<resource>database-service</resource>
<bearer-only>true</bearer-only>
</secure-deployment>
</subsystem>
Security Domain
The security context is propagated to the EJB tier automatically.
2.1.3. Installing JBoss EAP Adapter from an RPM
Install the EAP 7 Adapters from an RPM:
With Red Hat Enterprise Linux 7, the term channel was replaced with the term repository. In these instructions only the term repository is used. |
You must subscribe to the WildFly 20 repository before you can install the WildFly 7 adapters from an RPM.
Prerequisites
-
Ensure that your Red Hat Enterprise Linux system is registered to your account using Red Hat Subscription Manager. For more information see the Red Hat Subscription Management documentation.
-
If you are already subscribed to another JBoss EAP repository, you must unsubscribe from that repository first.
For Red Hat Enterprise Linux 6, 7: Using Red Hat Subscription Manager, subscribe to the WildFly 20 repository using the following command. Replace <RHEL_VERSION> with either 6 or 7 depending on your Red Hat Enterprise Linux version.
$ sudo subscription-manager repos --enable=jb-eap-7-for-rhel-<RHEL_VERSION>-server-rpms
For Red Hat Enterprise Linux 8: Using Red Hat Subscription Manager, subscribe to the WildFly 20 repository using the following command:
$ sudo subscription-manager repos --enable=jb-eap-20-for-rhel-8-x86_64-rpms --enable=rhel-8-for-x86_64-baseos-rpms --enable=rhel-8-for-x86_64-appstream-rpms
Install the WildFly 7 adapters for OIDC using the following command at Red Hat Enterprise Linux 6, 7:
$ sudo yum install eap7-keycloak-adapter-sso7_4
or use following one for Red Hat Enterprise Linux 8:
$ sudo dnf install eap7-keycloak-adapter-sso7_4
The default EAP_HOME path for the RPM installation is /opt/rh/eap7/root/usr/share/wildfly. |
Run the appropriate module installation script.
For the OIDC module, enter the following command:
$ $EAP_HOME/bin/jboss-cli.sh -c --file=$EAP_HOME/bin/adapter-install.cli
Your installation is complete.
Install the EAP 6 Adapters from an RPM:
With Red Hat Enterprise Linux 7, the term channel was replaced with the term repository. In these instructions only the term repository is used. |
You must subscribe to the JBoss EAP 6 repository before you can install the EAP 6 adapters from an RPM.
Prerequisites
-
Ensure that your Red Hat Enterprise Linux system is registered to your account using Red Hat Subscription Manager. For more information see the Red Hat Subscription Management documentation.
-
If you are already subscribed to another JBoss EAP repository, you must unsubscribe from that repository first.
Using Red Hat Subscription Manager, subscribe to the JBoss EAP 6 repository using the following command. Replace <RHEL_VERSION> with either 6 or 7 depending on your Red Hat Enterprise Linux version.
$ sudo subscription-manager repos --enable=jb-eap-6-for-rhel-<RHEL_VERSION>-server-rpms
Install the EAP 6 adapters for OIDC using the following command:
$ sudo yum install keycloak-adapter-sso7_4-eap6
The default EAP_HOME path for the RPM installation is /opt/rh/eap6/root/usr/share/wildfly. |
Run the appropriate module installation script.
For the OIDC module, enter the following command:
$ $EAP_HOME/bin/jboss-cli.sh -c --file=$EAP_HOME/bin/adapter-install.cli
Your installation is complete.
2.1.4. JBoss Fuse 6 Adapter
Keycloak supports securing your web applications running inside JBoss Fuse 6.
JBoss Fuse 6 leverages Jetty 9 adapter as JBoss Fuse 6.3.0 Rollup 12 is bundled with Jetty 9.2 server
under the covers and Jetty is used for running various kinds of web applications.
The only supported version of Fuse 6 is the latest release. If you use earlier versions of Fuse 6, it is possible that some functions will not work correctly. In particular, the Hawtio integration will not work with earlier versions of Fuse 6. |
Security for the following items is supported for Fuse:
-
Classic WAR applications deployed on Fuse with Pax Web War Extender
-
Servlets deployed on Fuse as OSGI services with Pax Web Whiteboard Extender
-
Apache Camel Jetty endpoints running with the Camel Jetty component
-
Apache CXF endpoints running on their own separate Jetty engine
-
Apache CXF endpoints running on the default engine provided by the CXF servlet
-
SSH and JMX admin access
-
Hawtio administration console
Securing Your Web Applications Inside Fuse 6
You must first install the Keycloak Karaf feature. Next you will need to perform the steps according to the type of application you want to secure.
All referenced web applications require injecting the Keycloak Jetty authenticator into the underlying Jetty server. The steps to achieve this depend on the application type. The details are described below.
The best place to start is look at Fuse demo bundled as part of Keycloak examples in directory fuse
. Most of the steps should be understandable from testing and understanding the demo.
Installing the Keycloak Feature
You must first install the keycloak
feature in the JBoss Fuse environment. The keycloak feature includes the Fuse adapter and all third-party dependencies. You can install it either from the Maven repository or from an archive.
Installing from the Maven Repository
As a prerequisite, you must be online and have access to the Maven repository.
For community it’s sufficient to be online as all the artifacts and 3rd party dependencies should be available in the maven central repository.
To install the keycloak feature using the Maven repository, complete the following steps:
-
Start JBoss Fuse 6.3.0 Rollup 12; then in the Karaf terminal type:
features:addurl mvn:org.keycloak/keycloak-osgi-features/11.0.3/xml/features features:install keycloak
-
You might also need to install the Jetty 9 feature:
features:install keycloak-jetty9-adapter
-
Ensure that the features were installed:
features:list | grep keycloak
Installing from the ZIP bundle
This is useful if you are offline or do not want to use Maven to obtain the JAR files and other artifacts.
To install the Fuse adapter from the ZIP archive, complete the following steps:
-
Download the Keycloak Fuse adapter ZIP archive.
-
Unzip it into the root directory of JBoss Fuse. The dependencies are then installed under the
system
directory. You can overwrite all existing jar files.Use this for JBoss Fuse 6.3.0 Rollup 12:
cd /path-to-fuse/jboss-fuse-6.3.0.redhat-254 unzip -q /path-to-adapter-zip/keycloak-fuse-adapter-11.0.3.zip
-
Start Fuse and run these commands in the fuse/karaf terminal:
features:addurl mvn:org.keycloak/keycloak-osgi-features/11.0.3/xml/features features:install keycloak
-
Install the corresponding Jetty adapter. Since the artifacts are available directly in the JBoss Fuse
system
directory, you do not need to use the Maven repository.
Securing a Classic WAR Application
The needed steps to secure your WAR application are:
-
In the
/WEB-INF/web.xml
file, declare the necessary:-
security constraints in the <security-constraint> element
-
login configuration in the <login-config> element
-
security roles in the <security-role> element.
For example:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <module-name>customer-portal</module-name> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> <security-constraint> <web-resource-collection> <web-resource-name>Customers</web-resource-name> <url-pattern>/customers/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>user</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>BASIC</auth-method> <realm-name>does-not-matter</realm-name> </login-config> <security-role> <role-name>admin</role-name> </security-role> <security-role> <role-name>user</role-name> </security-role> </web-app>
-
-
Add the
jetty-web.xml
file with the authenticator to the/WEB-INF/jetty-web.xml
file.For example:
<?xml version="1.0"?> <!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd"> <Configure class="org.eclipse.jetty.webapp.WebAppContext"> <Get name="securityHandler"> <Set name="authenticator"> <New class="org.keycloak.adapters.jetty.KeycloakJettyAuthenticator"> </New> </Set> </Get> </Configure>
-
Within the
/WEB-INF/
directory of your WAR, create a new file, keycloak.json. The format of this configuration file is described in the Java Adapters Config section. It is also possible to make this file available externally as described in Configuring the External Adapter. -
Ensure your WAR application imports
org.keycloak.adapters.jetty
and maybe some more packages in theMETA-INF/MANIFEST.MF
file, under theImport-Package
header. Usingmaven-bundle-plugin
in your project properly generates OSGI headers in manifest.
Note that «*» resolution for the package does not import theorg.keycloak.adapters.jetty
package, since it is not used by the application or the Blueprint or Spring descriptor, but is rather used in thejetty-web.xml
file.The list of the packages to import might look like this:
org.keycloak.adapters.jetty;version="11.0.3", org.keycloak.adapters;version="11.0.3", org.keycloak.constants;version="11.0.3", org.keycloak.util;version="11.0.3", org.keycloak.*;version="11.0.3", *;resolution:=optional
Configuring the External Adapter
If you do not want the keycloak.json
adapter configuration file to be bundled inside your WAR application, but instead made available externally and loaded based on naming conventions, use this configuration method.
To enable the functionality, add this section to your /WEB_INF/web.xml
file:
<context-param>
<param-name>keycloak.config.resolver</param-name>
<param-value>org.keycloak.adapters.osgi.PathBasedKeycloakConfigResolver</param-value>
</context-param>
That component uses keycloak.config
or karaf.etc
java properties to search for a base folder to locate the configuration.
Then inside one of those folders it searches for a file called <your_web_context>-keycloak.json
.
So, for example, if your web application has context my-portal
, then your adapter configuration is loaded from the $FUSE_HOME/etc/my-portal-keycloak.json
file.
Securing a Servlet Deployed as an OSGI Service
You can use this method if you have a servlet class inside your OSGI bundled project that is not deployed as a classic WAR application. Fuse uses Pax Web Whiteboard Extender to deploy such servlets as web applications.
To secure your servlet with Keycloak, complete the following steps:
-
Keycloak provides PaxWebIntegrationService, which allows injecting jetty-web.xml and configuring security constraints for your application. You need to declare such services in the
OSGI-INF/blueprint/blueprint.xml
file inside your application. Note that your servlet needs to depend on it.
An example configuration:<?xml version="1.0" encoding="UTF-8"?> <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd"> <!-- Using jetty bean just for the compatibility with other fuse services --> <bean id="servletConstraintMapping" class="org.eclipse.jetty.security.ConstraintMapping"> <property name="constraint"> <bean class="org.eclipse.jetty.util.security.Constraint"> <property name="name" value="cst1"/> <property name="roles"> <list> <value>user</value> </list> </property> <property name="authenticate" value="true"/> <property name="dataConstraint" value="0"/> </bean> </property> <property name="pathSpec" value="/product-portal/*"/> </bean> <bean id="keycloakPaxWebIntegration" class="org.keycloak.adapters.osgi.PaxWebIntegrationService" init-method="start" destroy-method="stop"> <property name="jettyWebXmlLocation" value="/WEB-INF/jetty-web.xml" /> <property name="bundleContext" ref="blueprintBundleContext" /> <property name="constraintMappings"> <list> <ref component-id="servletConstraintMapping" /> </list> </property> </bean> <bean id="productServlet" class="org.keycloak.example.ProductPortalServlet" depends-on="keycloakPaxWebIntegration"> </bean> <service ref="productServlet" interface="javax.servlet.Servlet"> <service-properties> <entry key="alias" value="/product-portal" /> <entry key="servlet-name" value="ProductServlet" /> <entry key="keycloak.config.file" value="/keycloak.json" /> </service-properties> </service> </blueprint>
-
You might need to have the
WEB-INF
directory inside your project (even if your project is not a web application) and create the/WEB-INF/jetty-web.xml
and/WEB-INF/keycloak.json
files as in the Classic WAR application section.
Note you don’t need theweb.xml
file as the security-constraints are declared in the blueprint configuration file.
-
-
The
Import-Package
inMETA-INF/MANIFEST.MF
must contain at least these imports:org.keycloak.adapters.jetty;version="11.0.3", org.keycloak.adapters;version="11.0.3", org.keycloak.constants;version="11.0.3", org.keycloak.util;version="11.0.3", org.keycloak.*;version="11.0.3", *;resolution:=optional
Securing an Apache Camel Application
You can secure Apache Camel endpoints implemented with the camel-jetty component by adding the securityHandler with KeycloakJettyAuthenticator
and the proper security constraints injected. You can add the OSGI-INF/blueprint/blueprint.xml
file to your Camel application with a similar configuration as below. The roles, security constraint mappings, and Keycloak adapter configuration might differ slightly depending on your environment and needs.
For example:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:camel="http://camel.apache.org/schema/blueprint"
xsi:schemaLocation="
http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
http://camel.apache.org/schema/blueprint http://camel.apache.org/schema/blueprint/camel-blueprint.xsd">
<bean id="kcAdapterConfig" class="org.keycloak.representations.adapters.config.AdapterConfig">
<property name="realm" value="demo"/>
<property name="resource" value="admin-camel-endpoint"/>
<property name="bearerOnly" value="true"/>
<property name="authServerUrl" value="http://localhost:8080/auth" />
<property name="sslRequired" value="EXTERNAL"/>
</bean>
<bean id="keycloakAuthenticator" class="org.keycloak.adapters.jetty.KeycloakJettyAuthenticator">
<property name="adapterConfig" ref="kcAdapterConfig"/>
</bean>
<bean id="constraint" class="org.eclipse.jetty.util.security.Constraint">
<property name="name" value="Customers"/>
<property name="roles">
<list>
<value>admin</value>
</list>
</property>
<property name="authenticate" value="true"/>
<property name="dataConstraint" value="0"/>
</bean>
<bean id="constraintMapping" class="org.eclipse.jetty.security.ConstraintMapping">
<property name="constraint" ref="constraint"/>
<property name="pathSpec" value="/*"/>
</bean>
<bean id="securityHandler" class="org.eclipse.jetty.security.ConstraintSecurityHandler">
<property name="authenticator" ref="keycloakAuthenticator" />
<property name="constraintMappings">
<list>
<ref component-id="constraintMapping" />
</list>
</property>
<property name="authMethod" value="BASIC"/>
<property name="realmName" value="does-not-matter"/>
</bean>
<bean id="sessionHandler" class="org.keycloak.adapters.jetty.spi.WrappingSessionHandler">
<property name="handler" ref="securityHandler" />
</bean>
<bean id="helloProcessor" class="org.keycloak.example.CamelHelloProcessor" />
<camelContext id="blueprintContext"
trace="false"
xmlns="http://camel.apache.org/schema/blueprint">
<route id="httpBridge">
<from uri="jetty:http://0.0.0.0:8383/admin-camel-endpoint?handlers=sessionHandler&matchOnUriPrefix=true" />
<process ref="helloProcessor" />
<log message="The message from camel endpoint contains ${body}"/>
</route>
</camelContext>
</blueprint>
-
The
Import-Package
inMETA-INF/MANIFEST.MF
needs to contain these imports:
javax.servlet;version="[3,4)",
javax.servlet.http;version="[3,4)",
org.apache.camel.*,
org.apache.camel;version="[2.13,3)",
org.eclipse.jetty.security;version="[9,10)",
org.eclipse.jetty.server.nio;version="[9,10)",
org.eclipse.jetty.util.security;version="[9,10)",
org.keycloak.*;version="11.0.3",
org.osgi.service.blueprint,
org.osgi.service.blueprint.container,
org.osgi.service.event,
Camel RestDSL
Camel RestDSL is a Camel feature used to define your REST endpoints in a fluent way. But you must still use specific implementation classes and provide instructions on how to integrate with Keycloak.
The way to configure the integration mechanism depends on the Camel component for which you configure your RestDSL-defined routes.
The following example shows how to configure integration using the Jetty component, with references to some of the beans defined in previous Blueprint example.
<bean id="securityHandlerRest" class="org.eclipse.jetty.security.ConstraintSecurityHandler">
<property name="authenticator" ref="keycloakAuthenticator" />
<property name="constraintMappings">
<list>
<ref component-id="constraintMapping" />
</list>
</property>
<property name="authMethod" value="BASIC"/>
<property name="realmName" value="does-not-matter"/>
</bean>
<bean id="sessionHandlerRest" class="org.keycloak.adapters.jetty.spi.WrappingSessionHandler">
<property name="handler" ref="securityHandlerRest" />
</bean>
<camelContext id="blueprintContext"
trace="false"
xmlns="http://camel.apache.org/schema/blueprint">
<restConfiguration component="jetty" contextPath="/restdsl"
port="8484">
<!--the link with Keycloak security handlers happens here-->
<endpointProperty key="handlers" value="sessionHandlerRest"></endpointProperty>
<endpointProperty key="matchOnUriPrefix" value="true"></endpointProperty>
</restConfiguration>
<rest path="/hello" >
<description>Hello rest service</description>
<get uri="/{id}" outType="java.lang.String">
<description>Just an helllo</description>
<to uri="direct:justDirect" />
</get>
</rest>
<route id="justDirect">
<from uri="direct:justDirect"/>
<process ref="helloProcessor" />
<log message="RestDSL correctly invoked ${body}"/>
<setBody>
<constant>(__This second sentence is returned from a Camel RestDSL endpoint__)</constant>
</setBody>
</route>
</camelContext>
Securing an Apache CXF Endpoint on a Separate Jetty Engine
To run your CXF endpoints secured by Keycloak on separate Jetty engines, complete the following steps:
-
Add
META-INF/spring/beans.xml
to your application, and in it, declarehttpj:engine-factory
with Jetty SecurityHandler with injectedKeycloakJettyAuthenticator
. The configuration for a CFX JAX-WS application might resemble this one:<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:httpj="http://cxf.apache.org/transports/http-jetty/configuration" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd http://cxf.apache.org/transports/http-jetty/configuration http://cxf.apache.org/schemas/configuration/http-jetty.xsd"> <import resource="classpath:META-INF/cxf/cxf.xml" /> <bean id="kcAdapterConfig" class="org.keycloak.representations.adapters.config.AdapterConfig"> <property name="realm" value="demo"/> <property name="resource" value="custom-cxf-endpoint"/> <property name="bearerOnly" value="true"/> <property name="authServerUrl" value="http://localhost:8080/auth" /> <property name="sslRequired" value="EXTERNAL"/> </bean> <bean id="keycloakAuthenticator" class="org.keycloak.adapters.jetty.KeycloakJettyAuthenticator"> <property name="adapterConfig"> <ref local="kcAdapterConfig" /> </property> </bean> <bean id="constraint" class="org.eclipse.jetty.util.security.Constraint"> <property name="name" value="Customers"/> <property name="roles"> <list> <value>user</value> </list> </property> <property name="authenticate" value="true"/> <property name="dataConstraint" value="0"/> </bean> <bean id="constraintMapping" class="org.eclipse.jetty.security.ConstraintMapping"> <property name="constraint" ref="constraint"/> <property name="pathSpec" value="/*"/> </bean> <bean id="securityHandler" class="org.eclipse.jetty.security.ConstraintSecurityHandler"> <property name="authenticator" ref="keycloakAuthenticator" /> <property name="constraintMappings"> <list> <ref local="constraintMapping" /> </list> </property> <property name="authMethod" value="BASIC"/> <property name="realmName" value="does-not-matter"/> </bean> <httpj:engine-factory bus="cxf" id="kc-cxf-endpoint"> <httpj:engine port="8282"> <httpj:handlers> <ref local="securityHandler" /> </httpj:handlers> <httpj:sessionSupport>true</httpj:sessionSupport> </httpj:engine> </httpj:engine-factory> <jaxws:endpoint implementor="org.keycloak.example.ws.ProductImpl" address="http://localhost:8282/ProductServiceCF" depends-on="kc-cxf-endpoint" /> </beans>
For the CXF JAX-RS application, the only difference might be in the configuration of the endpoint dependent on engine-factory:
<jaxrs:server serviceClass="org.keycloak.example.rs.CustomerService" address="http://localhost:8282/rest" depends-on="kc-cxf-endpoint"> <jaxrs:providers> <bean class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider" /> </jaxrs:providers> </jaxrs:server>
-
The
Import-Package
inMETA-INF/MANIFEST.MF
must contain those imports:
META-INF.cxf;version="[2.7,3.2)",
META-INF.cxf.osgi;version="[2.7,3.2)";resolution:=optional,
org.apache.cxf.bus;version="[2.7,3.2)",
org.apache.cxf.bus.spring;version="[2.7,3.2)",
org.apache.cxf.bus.resource;version="[2.7,3.2)",
org.apache.cxf.transport.http;version="[2.7,3.2)",
org.apache.cxf.*;version="[2.7,3.2)",
org.springframework.beans.factory.config,
org.eclipse.jetty.security;version="[9,10)",
org.eclipse.jetty.util.security;version="[9,10)",
org.keycloak.*;version="11.0.3"
Securing an Apache CXF Endpoint on the Default Jetty Engine
Some services automatically come with deployed servlets on startup. One such service is the CXF servlet running in the http://localhost:8181/cxf context. Securing such endpoints can be complicated. One approach, which Keycloak is currently using, is ServletReregistrationService, which undeploys a built-in servlet at startup, enabling you to redeploy it on a context secured by Keycloak.
The configuration file OSGI-INF/blueprint/blueprint.xml
inside your application might resemble the one below. Note that it adds the JAX-RS customerservice
endpoint, which is endpoint-specific to your application, but more importantly, secures the entire /cxf
context.
<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxrs="http://cxf.apache.org/blueprint/jaxrs"
xsi:schemaLocation="
http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
http://cxf.apache.org/blueprint/jaxrs http://cxf.apache.org/schemas/blueprint/jaxrs.xsd">
<!-- JAXRS Application -->
<bean id="customerBean" class="org.keycloak.example.rs.CxfCustomerService" />
<jaxrs:server id="cxfJaxrsServer" address="/customerservice">
<jaxrs:providers>
<bean class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider" />
</jaxrs:providers>
<jaxrs:serviceBeans>
<ref component-id="customerBean" />
</jaxrs:serviceBeans>
</jaxrs:server>
<!-- Securing of whole /cxf context by unregister default cxf servlet from paxweb and re-register with applied security constraints -->
<bean id="cxfConstraintMapping" class="org.eclipse.jetty.security.ConstraintMapping">
<property name="constraint">
<bean class="org.eclipse.jetty.util.security.Constraint">
<property name="name" value="cst1"/>
<property name="roles">
<list>
<value>user</value>
</list>
</property>
<property name="authenticate" value="true"/>
<property name="dataConstraint" value="0"/>
</bean>
</property>
<property name="pathSpec" value="/cxf/*"/>
</bean>
<bean id="cxfKeycloakPaxWebIntegration" class="org.keycloak.adapters.osgi.PaxWebIntegrationService"
init-method="start" destroy-method="stop">
<property name="bundleContext" ref="blueprintBundleContext" />
<property name="jettyWebXmlLocation" value="/WEB-INF/jetty-web.xml" />
<property name="constraintMappings">
<list>
<ref component-id="cxfConstraintMapping" />
</list>
</property>
</bean>
<bean id="defaultCxfReregistration" class="org.keycloak.adapters.osgi.ServletReregistrationService" depends-on="cxfKeycloakPaxWebIntegration"
init-method="start" destroy-method="stop">
<property name="bundleContext" ref="blueprintBundleContext" />
<property name="managedServiceReference">
<reference interface="org.osgi.service.cm.ManagedService" filter="(service.pid=org.apache.cxf.osgi)" timeout="5000" />
</property>
</bean>
</blueprint>
As a result, all other CXF services running on the default CXF HTTP destination are also secured. Similarly, when the application is undeployed, the entire /cxf
context becomes unsecured as well. For this reason, using your own Jetty engine for your applications as described in Secure CXF Application on separate Jetty Engine then gives you more
control over security for each individual application.
-
The
WEB-INF
directory might need to be inside your project (even if your project is not a web application). You might also need to edit the/WEB-INF/jetty-web.xml
and/WEB-INF/keycloak.json
files in a similar way as in Classic WAR application.
Note that you do not need theweb.xml
file as the security constraints are declared in the blueprint configuration file. -
The
Import-Package
inMETA-INF/MANIFEST.MF
must contain these imports:
META-INF.cxf;version="[2.7,3.2)",
META-INF.cxf.osgi;version="[2.7,3.2)";resolution:=optional,
org.apache.cxf.transport.http;version="[2.7,3.2)",
org.apache.cxf.*;version="[2.7,3.2)",
com.fasterxml.jackson.jaxrs.json;version="[2.5,3)",
org.eclipse.jetty.security;version="[9,10)",
org.eclipse.jetty.util.security;version="[9,10)",
org.keycloak.*;version="11.0.3",
org.keycloak.adapters.jetty;version="11.0.3",
*;resolution:=optional
Securing Fuse Administration Services
Using SSH Authentication to Fuse Terminal
Keycloak mainly addresses use cases for authentication of web applications; however, if your other web services and applications are protected
with Keycloak, protecting non-web administration services such as SSH with Keycloak credentials is a best pracrice. You can do this using the JAAS login module, which allows remote connection to Keycloak and verifies credentials based on
Resource Owner Password Credentials.
To enable SSH authentication, complete the following steps:
-
In Keycloak create a client (for example,
ssh-jmx-admin-client
), which will be used for SSH authentication.
This client needs to haveDirect Access Grants Enabled
selected toOn
. -
In the
$FUSE_HOME/etc/org.apache.karaf.shell.cfg
file, update or specify this property: -
Add the
$FUSE_HOME/etc/keycloak-direct-access.json
file with content similar to the following (based on your environment and Keycloak client settings):{ "realm": "demo", "resource": "ssh-jmx-admin-client", "ssl-required" : "external", "auth-server-url" : "http://localhost:8080/auth", "credentials": { "secret": "password" } }
This file specifies the client application configuration, which is used by JAAS DirectAccessGrantsLoginModule from the
keycloak
JAAS realm for SSH authentication. -
Start Fuse and install the
keycloak
JAAS realm. The easiest way is to install thekeycloak-jaas
feature, which has the JAAS realm predefined. You can override the feature’s predefined realm by using your ownkeycloak
JAAS realm with higher ranking. For details see the JBoss Fuse documentation.Use these commands in the Fuse terminal:
features:addurl mvn:org.keycloak/keycloak-osgi-features/11.0.3/xml/features features:install keycloak-jaas
-
Log in using SSH as
admin
user by typing the following in the terminal:ssh -o PubkeyAuthentication=no -p 8101 admin@localhost
-
Log in with password
password
.
On some later operating systems, you might also need to use the SSH command’s -o option -o HostKeyAlgorithms=+ssh-dss because later SSH clients do not allow use of the ssh-dss algorithm, by default. However, by default, it is currently used in JBoss Fuse 6.3.0 Rollup 12.
|
Note that the user needs to have realm role admin
to perform all operations or another role to perform a subset of operations (for example, the viewer role that restricts the user to run only read-only Karaf commands). The available roles are configured in $FUSE_HOME/etc/org.apache.karaf.shell.cfg
or $FUSE_HOME/etc/system.properties
.
Using JMX Authentication
JMX authentication might be necessary if you want to use jconsole or another external tool to remotely connect to JMX through RMI. Otherwise it might be better to use hawt.io/jolokia, since the jolokia agent is installed in hawt.io by default. For more details see Hawtio Admin Console.
To use JMX authentication, complete the following steps:
-
In the
$FUSE_HOME/etc/org.apache.karaf.management.cfg
file, change the jmxRealm property to: -
Install the
keycloak-jaas
feature and configure the$FUSE_HOME/etc/keycloak-direct-access.json
file as described in the SSH section above. -
In jconsole you can use a URL such as:
service:jmx:rmi://localhost:44444/jndi/rmi://localhost:1099/karaf-root
and credentials: admin/password (based on the user with admin privileges according to your environment).
Securing the Hawtio Administration Console
To secure the Hawtio Administration Console with Keycloak, complete the following steps:
-
Add these properties to the
$FUSE_HOME/etc/system.properties
file:hawtio.keycloakEnabled=true hawtio.realm=keycloak hawtio.keycloakClientConfig=file://${karaf.base}/etc/keycloak-hawtio-client.json hawtio.rolePrincipalClasses=org.keycloak.adapters.jaas.RolePrincipal,org.apache.karaf.jaas.boot.principal.RolePrincipal
-
Create a client in the Keycloak administration console in your realm. For example, in the Keycloak
demo
realm, create a clienthawtio-client
, specifypublic
as the Access Type, and specify a redirect URI pointing to Hawtio: http://localhost:8181/hawtio/*. You must also have a corresponding Web Origin configured (in this case, http://localhost:8181). -
Create the
keycloak-hawtio-client.json
file in the$FUSE_HOME/etc
directory using content similar to that shown in the example below. Change therealm
,resource
, andauth-server-url
properties according to your Keycloak environment. Theresource
property must point to the client created in the previous step. This file is used by the client (Hawtio JavaScript application) side.{ "realm" : "demo", "resource" : "hawtio-client", "auth-server-url" : "http://localhost:8080/auth", "ssl-required" : "external", "public-client" : true }
-
Create the
keycloak-hawtio.json
file in the$FUSE_HOME/etc
dicrectory using content similar to that shown in the example below. Change therealm
andauth-server-url
properties according to your Keycloak environment. This file is used by the adapters on the server (JAAS Login module) side.{ "realm" : "demo", "resource" : "jaas", "bearer-only" : true, "auth-server-url" : "http://localhost:8080/auth", "ssl-required" : "external", "use-resource-role-mappings": false, "principal-attribute": "preferred_username" }
-
Start JBoss Fuse 6.3.0 Rollup 12 and install the keycloak feature if you have not already done so. The commands in Karaf terminal are similar to this example:
features:addurl mvn:org.keycloak/keycloak-osgi-features/11.0.3/xml/features features:install keycloak
-
Go to http://localhost:8181/hawtio and log in as a user from your Keycloak realm.
Note that the user needs to have the proper realm role to successfully authenticate to Hawtio. The available roles are configured in the
$FUSE_HOME/etc/system.properties
file inhawtio.roles
.
Securing Hawtio on JBoss EAP 6.4
To run Hawtio on the JBoss EAP 6.4 server, complete the following steps:
-
Set up Keycloak as described in the previous section, Securing the Hawtio Administration Console. It is assumed that:
-
you have a Keycloak realm
demo
and clienthawtio-client
-
your Keycloak is running on
localhost:8080
-
the JBoss EAP 6.4 server with deployed Hawtio will be running on
localhost:8181
. The directory with this server is referred in next steps as$EAP_HOME
.
-
-
Copy the
hawtio-wildfly-1.4.0.redhat-630396.war
archive to the$EAP_HOME/standalone/configuration
directory. For more details about deploying Hawtio see the Fuse Hawtio documentation. -
Copy the
keycloak-hawtio.json
andkeycloak-hawtio-client.json
files with the above content to the$EAP_HOME/standalone/configuration
directory. -
Install the Keycloak adapter subsystem to your JBoss EAP 6.4 server as described in the JBoss adapter documentation.
-
In the
$EAP_HOME/standalone/configuration/standalone.xml
file configure the system properties as in this example:<extensions> ... </extensions> <system-properties> <property name="hawtio.authenticationEnabled" value="true" /> <property name="hawtio.realm" value="hawtio" /> <property name="hawtio.roles" value="admin,viewer" /> <property name="hawtio.rolePrincipalClasses" value="org.keycloak.adapters.jaas.RolePrincipal" /> <property name="hawtio.keycloakEnabled" value="true" /> <property name="hawtio.keycloakClientConfig" value="${jboss.server.config.dir}/keycloak-hawtio-client.json" /> <property name="hawtio.keycloakServerConfig" value="${jboss.server.config.dir}/keycloak-hawtio.json" /> </system-properties>
-
Add the Hawtio realm to the same file in the
security-domains
section:<security-domain name="hawtio" cache-type="default"> <authentication> <login-module code="org.keycloak.adapters.jaas.BearerTokenLoginModule" flag="required"> <module-option name="keycloak-config-file" value="${hawtio.keycloakServerConfig}"/> </login-module> </authentication> </security-domain>
-
Add the
secure-deployment
sectionhawtio
to the adapter subsystem. This ensures that the Hawtio WAR is able to find the JAAS login module classes.<subsystem xmlns="urn:jboss:domain:keycloak:1.1"> <secure-deployment name="hawtio-wildfly-1.4.0.redhat-630396.war" /> </subsystem>
-
Restart the JBoss EAP 6.4 server with Hawtio:
cd $EAP_HOME/bin ./standalone.sh -Djboss.socket.binding.port-offset=101
-
Access Hawtio at http://localhost:8181/hawtio. It is secured by Keycloak.
2.1.5. JBoss Fuse 7 Adapter
Keycloak supports securing your web applications running inside JBoss Fuse 7.
JBoss Fuse 7 leverages Undertow adapter which is essentially the same as
EAP 7 / WildFly Adapter
as JBoss Fuse 7.4.0 is bundled with Undertow HTTP engine under the covers and Undertow is used for running various kinds of web applications.
The only supported version of Fuse 7 is the latest release. If you use earlier versions of Fuse 7, it is possible that some functions will not work correctly. In particular, integration will not work at all for versions of Fuse 7 lower than 7.0.1. |
Security for the following items is supported for Fuse:
-
Classic WAR applications deployed on Fuse with Pax Web War Extender
-
Servlets deployed on Fuse as OSGI services with Pax Web Whiteboard Extender and additionally servlets registered through
org.osgi.service.http.HttpService#registerServlet() which is standard OSGi Enterprise HTTP Service -
Apache Camel Undertow endpoints running with the Camel Undertow component
-
Apache CXF endpoints running on their own separate Undertow engine
-
Apache CXF endpoints running on the default engine provided by the CXF servlet
-
SSH and JMX admin access
-
Hawtio administration console
Securing Your Web Applications Inside Fuse 7
You must first install the Keycloak Karaf feature. Next you will need to perform the steps according to the type of application you want to secure.
All referenced web applications require injecting the Keycloak Undertow authentication mechanism into the underlying web server. The steps to achieve this depend on the application type. The details are described below.
The best place to start is look at Fuse demo bundled as part of Keycloak examples in directory fuse
. Most of the steps should be understandable from testing and understanding the demo.
Installing the Keycloak Feature
You must first install the keycloak-pax-http-undertow
and keycloak-jaas
features in the JBoss Fuse environment. The keycloak-pax-http-undertow
feature includes the Fuse adapter and all third-party dependencies. The keycloak-jaas
contains JAAS module used in realm for SSH and JMX authentication. You can install it either from the Maven repository or from an archive.
Installing from the Maven Repository
As a prerequisite, you must be online and have access to the Maven repository.
For community it’s sufficient to be online as all the artifacts and 3rd party dependencies should be available in the maven central repository.
To install the keycloak feature using the Maven repository, complete the following steps:
-
Start JBoss Fuse 7.4.0; then in the Karaf terminal type:
feature:repo-add mvn:org.keycloak/keycloak-osgi-features/11.0.3/xml/features feature:install keycloak-pax-http-undertow keycloak-jaas
-
You might also need to install the Undertow feature:
feature:install pax-http-undertow
-
Ensure that the features were installed:
feature:list | grep keycloak
Installing from the ZIP bundle
This is useful if you are offline or do not want to use Maven to obtain the JAR files and other artifacts.
To install the Fuse adapter from the ZIP archive, complete the following steps:
-
Download the Keycloak Fuse adapter ZIP archive.
-
Unzip it into the root directory of JBoss Fuse. The dependencies are then installed under the
system
directory. You can overwrite all existing jar files.Use this for JBoss Fuse 7.4.0:
cd /path-to-fuse/fuse-karaf-7.z unzip -q /path-to-adapter-zip/keycloak-fuse-adapter-11.0.3.zip
-
Start Fuse and run these commands in the fuse/karaf terminal:
feature:repo-add mvn:org.keycloak/keycloak-osgi-features/11.0.3/xml/features feature:install keycloak-pax-http-undertow keycloak-jaas
-
Install the corresponding Undertow adapter. Since the artifacts are available directly in the JBoss Fuse
system
directory, you do not need to use the Maven repository.
Securing a Classic WAR Application
The needed steps to secure your WAR application are:
-
In the
/WEB-INF/web.xml
file, declare the necessary:-
security constraints in the <security-constraint> element
-
login configuration in the <login-config> element. Make sure that the
<auth-method>
isKEYCLOAK
. -
security roles in the <security-role> element
For example:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <module-name>customer-portal</module-name> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> <security-constraint> <web-resource-collection> <web-resource-name>Customers</web-resource-name> <url-pattern>/customers/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>user</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>KEYCLOAK</auth-method> <realm-name>does-not-matter</realm-name> </login-config> <security-role> <role-name>admin</role-name> </security-role> <security-role> <role-name>user</role-name> </security-role> </web-app>
-
-
Within the
/WEB-INF/
directory of your WAR, create a new file, keycloak.json. The format of this configuration file is described in the Java Adapters Config section. It is also possible to make this file available externally as described in Configuring the External Adapter.For example:
{ "realm": "demo", "resource": "customer-portal", "auth-server-url": "http://localhost:8080/auth", "ssl-required" : "external", "credentials": { "secret": "password" } }
-
Contrary to the Fuse 6 adapter, there are no special OSGi imports needed in MANIFEST.MF.
Configuration Resolvers
The keycloak.json
adapter configuration file can be stored inside a bundle,
which is default behaviour, or in a directory on a filesystem. To specify the
actual source of the configuration file, set the keycloak.config.resolver
deployment parameter to the desired configuration resolver class.
For example, in a classic WAR application, set the keycloak.config.resolver
context parameter in web.xml
file like this:
<context-param>
<param-name>keycloak.config.resolver</param-name>
<param-value>org.keycloak.adapters.osgi.PathBasedKeycloakConfigResolver</param-value>
</context-param>
The following resolvers are available for keycloak.config.resolver
:
- org.keycloak.adapters.osgi.BundleBasedKeycloakConfigResolver
-
This is the default resolver. The configuration file is expected inside
the OSGi bundle that is being secured. By default, it loads file namedWEB-INF/keycloak.json
but this file name can be configured viaconfigLocation
property. - org.keycloak.adapters.osgi.PathBasedKeycloakConfigResolver
-
This resolver searches for a file called
<your_web_context>-keycloak.json
inside a folder
that is specified bykeycloak.config
system property. Ifkeycloak.config
is
not set,karaf.etc
system property is used instead.For example, if your web application is deployed into context
my-portal
, then
your adapter configuration would be loaded either from the
${keycloak.config}/my-portal-keycloak.json
file, or from${karaf.etc}/my-portal-keycloak.json
. - org.keycloak.adapters.osgi.HierarchicalPathBasedKeycloakConfigResolver
-
This resolver is similar to
PathBasedKeycloakConfigResolver
above, where
for given URI path, configuration locations are checked from most to least specific.For example, for
/my/web-app/context
URI, the following configuration locations are searched for existence until the first one exists:-
${karaf.etc}/my-web-app-context-keycloak.json
-
${karaf.etc}/my-web-app-keycloak.json
-
${karaf.etc}/my-keycloak.json
-
${karaf.etc}/keycloak.json
-
Securing a Servlet Deployed as an OSGI Service
You can use this method if you have a servlet class inside your OSGI bundled project that is not deployed as a classic WAR application. Fuse uses Pax Web Whiteboard Extender to deploy such servlets as web applications.
To secure your servlet with Keycloak, complete the following steps:
-
Keycloak provides
org.keycloak.adapters.osgi.undertow.PaxWebIntegrationService
, which allows configuring authentication method and security constraints for your application. You need to declare such services in theOSGI-INF/blueprint/blueprint.xml
file inside your application. Note that your servlet needs to depend on it.
An example configuration:<?xml version="1.0" encoding="UTF-8"?> <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd"> <bean id="servletConstraintMapping" class="org.keycloak.adapters.osgi.PaxWebSecurityConstraintMapping"> <property name="roles"> <list> <value>user</value> </list> </property> <property name="authentication" value="true"/> <property name="url" value="/product-portal/*"/> </bean> <!-- This handles the integration and setting the login-config and security-constraints parameters --> <bean id="keycloakPaxWebIntegration" class="org.keycloak.adapters.osgi.undertow.PaxWebIntegrationService" init-method="start" destroy-method="stop"> <property name="bundleContext" ref="blueprintBundleContext" /> <property name="constraintMappings"> <list> <ref component-id="servletConstraintMapping" /> </list> </property> </bean> <bean id="productServlet" class="org.keycloak.example.ProductPortalServlet" depends-on="keycloakPaxWebIntegration" /> <service ref="productServlet" interface="javax.servlet.Servlet"> <service-properties> <entry key="alias" value="/product-portal" /> <entry key="servlet-name" value="ProductServlet" /> <entry key="keycloak.config.file" value="/keycloak.json" /> </service-properties> </service> </blueprint>
-
You might need to have the
WEB-INF
directory inside your project (even if your project is not a web application) and create the/WEB-INF/keycloak.json
file as described in the Classic WAR application section.
Note you don’t need theweb.xml
file as the security-constraints are declared in the blueprint configuration file.
-
-
Contrary to the Fuse 6 adapter, there are no special OSGi imports needed in MANIFEST.MF.
Securing an Apache Camel Application
You can secure Apache Camel endpoints implemented with the camel-undertow component by injecting the proper security constraints via blueprint and updating the used component to undertow-keycloak
. You have to add the OSGI-INF/blueprint/blueprint.xml
file to your Camel application with a similar configuration as below. The roles, security constraint mappings, and adapter configuration might differ slightly depending on your environment and needs.
Compared to the standard undertow
component, undertow-keycloak
component adds two new properties:
-
configResolver
is a resolver bean that supplies Keycloak adapter
configuration. Available resolvers are listed in Configuration Resolvers section. -
allowedRoles
is a comma-separated list of roles. User accessing the service has to have at least one role to be permitted the access.
For example:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:camel="http://camel.apache.org/schema/blueprint"
xsi:schemaLocation="
http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
http://camel.apache.org/schema/blueprint http://camel.apache.org/schema/blueprint/camel-blueprint-2.17.1.xsd">
<bean id="keycloakConfigResolver" class="org.keycloak.adapters.osgi.BundleBasedKeycloakConfigResolver" >
<property name="bundleContext" ref="blueprintBundleContext" />
</bean>
<bean id="helloProcessor" class="org.keycloak.example.CamelHelloProcessor" />
<camelContext id="blueprintContext"
trace="false"
xmlns="http://camel.apache.org/schema/blueprint">
<route id="httpBridge">
<from uri="undertow-keycloak:http://0.0.0.0:8383/admin-camel-endpoint?matchOnUriPrefix=true&configResolver=#keycloakConfigResolver&allowedRoles=admin" />
<process ref="helloProcessor" />
<log message="The message from camel endpoint contains ${body}"/>
</route>
</camelContext>
</blueprint>
-
The
Import-Package
inMETA-INF/MANIFEST.MF
needs to contain these imports:
javax.servlet;version="[3,4)",
javax.servlet.http;version="[3,4)",
javax.net.ssl,
org.apache.camel.*,
org.apache.camel;version="[2.13,3)",
io.undertow.*,
org.keycloak.*;version="11.0.3",
org.osgi.service.blueprint,
org.osgi.service.blueprint.container
Camel RestDSL
Camel RestDSL is a Camel feature used to define your REST endpoints in a fluent way. But you must still use specific implementation classes and provide instructions on how to integrate with Keycloak.
The way to configure the integration mechanism depends on the Camel component for which you configure your RestDSL-defined routes.
The following example shows how to configure integration using the undertow-keycloak
component, with references to some of the beans defined in previous Blueprint example.
<camelContext id="blueprintContext"
trace="false"
xmlns="http://camel.apache.org/schema/blueprint">
<!--the link with Keycloak security handlers happens by using undertow-keycloak component -->
<restConfiguration apiComponent="undertow-keycloak" contextPath="/restdsl" port="8484">
<endpointProperty key="configResolver" value="#keycloakConfigResolver" />
<endpointProperty key="allowedRoles" value="admin,superadmin" />
</restConfiguration>
<rest path="/hello" >
<description>Hello rest service</description>
<get uri="/{id}" outType="java.lang.String">
<description>Just a hello</description>
<to uri="direct:justDirect" />
</get>
</rest>
<route id="justDirect">
<from uri="direct:justDirect"/>
<process ref="helloProcessor" />
<log message="RestDSL correctly invoked ${body}"/>
<setBody>
<constant>(__This second sentence is returned from a Camel RestDSL endpoint__)</constant>
</setBody>
</route>
</camelContext>
Securing an Apache CXF Endpoint on a Separate Undertow Engine
To run your CXF endpoints secured by Keycloak on a separate Undertow engine, complete the following steps:
-
Add
OSGI-INF/blueprint/blueprint.xml
to your application, and in it, add the proper configuration resolver bean similarly to Camel configuration.
In thehttpu:engine-factory
declareorg.keycloak.adapters.osgi.undertow.CxfKeycloakAuthHandler
handler using that camel configuration. The configuration for a CFX JAX-WS application might resemble this one:<?xml version="1.0" encoding="UTF-8"?> <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/blueprint/jaxws" xmlns:cxf="http://cxf.apache.org/blueprint/core" xmlns:httpu="http://cxf.apache.org/transports/http-undertow/configuration". xsi:schemaLocation=" http://cxf.apache.org/transports/http-undertow/configuration http://cxf.apache.org/schemas/configuration/http-undertow.xsd http://cxf.apache.org/blueprint/core http://cxf.apache.org/schemas/blueprint/core.xsd http://cxf.apache.org/blueprint/jaxws http://cxf.apache.org/schemas/blueprint/jaxws.xsd"> <bean id="keycloakConfigResolver" class="org.keycloak.adapters.osgi.BundleBasedKeycloakConfigResolver" > <property name="bundleContext" ref="blueprintBundleContext" /> </bean> <httpu:engine-factory bus="cxf" id="kc-cxf-endpoint"> <httpu:engine port="8282"> <httpu:handlers> <bean class="org.keycloak.adapters.osgi.undertow.CxfKeycloakAuthHandler"> <property name="configResolver" ref="keycloakConfigResolver" /> </bean> </httpu:handlers> </httpu:engine> </httpu:engine-factory> <jaxws:endpoint implementor="org.keycloak.example.ws.ProductImpl" address="http://localhost:8282/ProductServiceCF" depends-on="kc-cxf-endpoint"/> </blueprint>
For the CXF JAX-RS application, the only difference might be in the configuration of the endpoint dependent on engine-factory:
<jaxrs:server serviceClass="org.keycloak.example.rs.CustomerService" address="http://localhost:8282/rest" depends-on="kc-cxf-endpoint"> <jaxrs:providers> <bean class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider" /> </jaxrs:providers> </jaxrs:server>
-
The
Import-Package
inMETA-INF/MANIFEST.MF
must contain those imports:
META-INF.cxf;version="[2.7,3.3)",
META-INF.cxf.osgi;version="[2.7,3.3)";resolution:=optional,
org.apache.cxf.bus;version="[2.7,3.3)",
org.apache.cxf.bus.spring;version="[2.7,3.3)",
org.apache.cxf.bus.resource;version="[2.7,3.3)",
org.apache.cxf.transport.http;version="[2.7,3.3)",
org.apache.cxf.*;version="[2.7,3.3)",
org.springframework.beans.factory.config,
org.keycloak.*;version="11.0.3"
Securing an Apache CXF Endpoint on the Default Undertow Engine
Some services automatically come with deployed servlets on startup. One such service is the CXF servlet running in the http://localhost:8181/cxf context. Fuse’s Pax Web supports altering existing contexts via configuration admin. This can be used to secure endpoints by Keycloak.
The configuration file OSGI-INF/blueprint/blueprint.xml
inside your application might resemble the one below. Note that it adds the JAX-RS customerservice
endpoint, which is endpoint-specific to your application.
<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxrs="http://cxf.apache.org/blueprint/jaxrs"
xsi:schemaLocation="
http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
http://cxf.apache.org/blueprint/jaxrs http://cxf.apache.org/schemas/blueprint/jaxrs.xsd">
<!-- JAXRS Application -->
<bean id="customerBean" class="org.keycloak.example.rs.CxfCustomerService" />
<jaxrs:server id="cxfJaxrsServer" address="/customerservice">
<jaxrs:providers>
<bean class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider" />
</jaxrs:providers>
<jaxrs:serviceBeans>
<ref component-id="customerBean" />
</jaxrs:serviceBeans>
</jaxrs:server>
</blueprint>
Furthermore, you have to create ${karaf.etc}/org.ops4j.pax.web.context-anyName.cfg file
. It will be treated as factory PID configuration that is tracked by pax-web-runtime
bundle. Such configuration may contain the following properties that correspond to some of the properties of standard web.xml
:
bundle.symbolicName = org.apache.cxf.cxf-rt-transports-http
context.id = default
context.param.keycloak.config.resolver = org.keycloak.adapters.osgi.HierarchicalPathBasedKeycloakConfigResolver
login.config.authMethod = KEYCLOAK
security.cxf.url = /cxf/customerservice/*
security.cxf.roles = admin, user
For full description of available properties in configuration admin file, please refer to Fuse documentation. The properties above have the following meaning:
bundle.symbolicName
andcontext.id
-
Identification of the bundle and its deployment context within
org.ops4j.pax.web.service.WebContainer
. context.param.keycloak.config.resolver
-
Provides value of
keycloak.config.resolver
context parameter to the bundle just the same as inweb.xml
for classic WARs. Available resolvers are described in Configuration Resolvers section. login.config.authMethod
-
Authentication method. Must be
KEYCLOAK
. security.anyName.url
andsecurity.anyName.roles
-
Values of properties of individual security constraints just as they would be set in
security-constraint/web-resource-collection/url-pattern
andsecurity-constraint/auth-constraint/role-name
inweb.xml
, respectively. Roles are separated by comma and whitespace around it. TheanyName
identifier can be arbitrary but must match for individual properties of the same security constraint.Some Fuse versions contain a bug that requires roles to be separated by
", "
(comma and single space). Make sure you use precisely this notation for separating the roles.
The Import-Package
in META-INF/MANIFEST.MF
must contain at least these imports:
javax.ws.rs;version="[2,3)",
META-INF.cxf;version="[2.7,3.3)",
META-INF.cxf.osgi;version="[2.7,3.3)";resolution:=optional,
org.apache.cxf.transport.http;version="[2.7,3.3)",
org.apache.cxf.*;version="[2.7,3.3)",
com.fasterxml.jackson.jaxrs.json;version="${jackson.version}"
Securing Fuse Administration Services
Using SSH Authentication to Fuse Terminal
Keycloak mainly addresses use cases for authentication of web applications; however, if your other web services and applications are protected
with Keycloak, protecting non-web administration services such as SSH with Keycloak credentials is a best pracrice. You can do this using the JAAS login module, which allows remote connection to Keycloak and verifies credentials based on
Resource Owner Password Credentials.
To enable SSH authentication, complete the following steps:
-
In Keycloak create a client (for example,
ssh-jmx-admin-client
), which will be used for SSH authentication.
This client needs to haveDirect Access Grants Enabled
selected toOn
. -
In the
$FUSE_HOME/etc/org.apache.karaf.shell.cfg
file, update or specify this property: -
Add the
$FUSE_HOME/etc/keycloak-direct-access.json
file with content similar to the following (based on your environment and Keycloak client settings):{ "realm": "demo", "resource": "ssh-jmx-admin-client", "ssl-required" : "external", "auth-server-url" : "http://localhost:8080/auth", "credentials": { "secret": "password" } }
This file specifies the client application configuration, which is used by JAAS DirectAccessGrantsLoginModule from the
keycloak
JAAS realm for SSH authentication. -
Start Fuse and install the
keycloak
JAAS realm. The easiest way is to install thekeycloak-jaas
feature, which has the JAAS realm predefined. You can override the feature’s predefined realm by using your ownkeycloak
JAAS realm with higher ranking. For details see the JBoss Fuse documentation.Use these commands in the Fuse terminal:
features:addurl mvn:org.keycloak/keycloak-osgi-features/11.0.3/xml/features features:install keycloak-jaas
-
Log in using SSH as
admin
user by typing the following in the terminal:ssh -o PubkeyAuthentication=no -p 8101 admin@localhost
-
Log in with password
password
.
On some later operating systems, you might also need to use the SSH command’s -o option -o HostKeyAlgorithms=+ssh-dss because later SSH clients do not allow use of the ssh-dss algorithm, by default. However, by default, it is currently used in JBoss Fuse 7.4.0.
|
Note that the user needs to have realm role admin
to perform all operations or another role to perform a subset of operations (for example, the viewer role that restricts the user to run only read-only Karaf commands). The available roles are configured in $FUSE_HOME/etc/org.apache.karaf.shell.cfg
or $FUSE_HOME/etc/system.properties
.
Using JMX Authentication
JMX authentication might be necessary if you want to use jconsole or another external tool to remotely connect to JMX through RMI. Otherwise it might be better to use hawt.io/jolokia, since the jolokia agent is installed in hawt.io by default. For more details see Hawtio Admin Console.
To use JMX authentication, complete the following steps:
-
In the
$FUSE_HOME/etc/org.apache.karaf.management.cfg
file, change the jmxRealm property to: -
Install the
keycloak-jaas
feature and configure the$FUSE_HOME/etc/keycloak-direct-access.json
file as described in the SSH section above. -
In jconsole you can use a URL such as:
service:jmx:rmi://localhost:44444/jndi/rmi://localhost:1099/karaf-root
and credentials: admin/password (based on the user with admin privileges according to your environment).
Securing the Hawtio Administration Console
To secure the Hawtio Administration Console with Keycloak, complete the following steps:
-
Create a client in the Keycloak administration console in your realm. For example, in the Keycloak
demo
realm, create a clienthawtio-client
, specifypublic
as the Access Type, and specify a redirect URI pointing to Hawtio: http://localhost:8181/hawtio/*. Configure corresponding Web Origin (in this case, http://localhost:8181). Setup client scope mapping to include view-profile client role of account client in Scope tab inhawtio-client
client detail. -
Create the
keycloak-hawtio-client.json
file in the$FUSE_HOME/etc
directory using content similar to that shown in the example below. Change therealm
,resource
, andauth-server-url
properties according to your Keycloak environment. Theresource
property must point to the client created in the previous step. This file is used by the client (Hawtio JavaScript application) side.{ "realm" : "demo", "clientId" : "hawtio-client", "url" : "http://localhost:8080/auth", "ssl-required" : "external", "public-client" : true }
-
Create the
keycloak-direct-access.json
file in the$FUSE_HOME/etc
directory using content similar to that shown in the example below. Change therealm
andurl
properties according to your Keycloak environment. This file is used by JavaScript client.{ "realm" : "demo", "resource" : "ssh-jmx-admin-client", "auth-server-url" : "http://localhost:8080/auth", "ssl-required" : "external", "credentials": { "secret": "password" } }
-
Create the
keycloak-hawtio.json
file in the$FUSE_HOME/etc
dicrectory using content similar to that shown in the example below. Change therealm
andauth-server-url
properties according to your Keycloak environment. This file is used by the adapters on the server (JAAS Login module) side.{ "realm" : "demo", "resource" : "jaas", "bearer-only" : true, "auth-server-url" : "http://localhost:8080/auth", "ssl-required" : "external", "use-resource-role-mappings": false, "principal-attribute": "preferred_username" }
-
Start JBoss Fuse 7.4.0, install the Keycloak feature. Then type in the Karaf terminal:
system:property -p hawtio.keycloakEnabled true system:property -p hawtio.realm keycloak system:property -p hawtio.keycloakClientConfig file://${karaf.base}/etc/keycloak-hawtio-client.json system:property -p hawtio.rolePrincipalClasses org.keycloak.adapters.jaas.RolePrincipal,org.apache.karaf.jaas.boot.principal.RolePrincipal restart io.hawt.hawtio-war
-
Go to http://localhost:8181/hawtio and log in as a user from your Keycloak realm.
Note that the user needs to have the proper realm role to successfully authenticate to Hawtio. The available roles are configured in the
$FUSE_HOME/etc/system.properties
file inhawtio.roles
.
2.1.6. Spring Boot Adapter
To be able to secure Spring Boot apps you must add the Keycloak Spring Boot adapter JAR to your app.
You then have to provide some extra configuration via normal Spring Boot configuration (application.properties
). Let’s go over these steps.
Adapter Installation
The Keycloak Spring Boot adapter takes advantage of Spring Boot’s autoconfiguration so all you need to do is add the Keycloak Spring Boot starter to your project.
To add it using Maven, add the following to your dependencies:
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
</dependency>
Add the Adapter BOM dependency:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.keycloak.bom</groupId>
<artifactId>keycloak-adapter-bom</artifactId>
<version>11.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Currently the following embedded containers are supported and do not require any extra dependencies if using the Starter:
-
Tomcat
-
Undertow
-
Jetty
Required Spring Boot Adapter Configuration
This section describes how to configure your Spring Boot app to use Keycloak.
Instead of a keycloak.json
file, you configure the realm for the Spring Boot Keycloak adapter via the normal Spring Boot configuration.
For example:
keycloak.realm = demorealm
keycloak.auth-server-url = http://127.0.0.1:8080/auth
keycloak.ssl-required = external
keycloak.resource = demoapp
keycloak.credentials.secret = 11111111-1111-1111-1111-111111111111
keycloak.use-resource-role-mappings = true
You can disable the Keycloak Spring Boot Adapter (for example in tests) by setting keycloak.enabled = false
.
To configure a Policy Enforcer, unlike keycloak.json, policy-enforcer-config
must be used instead of just policy-enforcer
.
You also need to specify the Java EE security config that would normally go in the web.xml
.
The Spring Boot Adapter will set the login-method
to KEYCLOAK
and configure the security-constraints
at startup time.
Here’s an example configuration:
keycloak.securityConstraints[0].authRoles[0] = admin
keycloak.securityConstraints[0].authRoles[1] = user
keycloak.securityConstraints[0].securityCollections[0].name = insecure stuff
keycloak.securityConstraints[0].securityCollections[0].patterns[0] = /insecure
keycloak.securityConstraints[1].authRoles[0] = admin
keycloak.securityConstraints[1].securityCollections[0].name = admin stuff
keycloak.securityConstraints[1].securityCollections[0].patterns[0] = /admin
If you plan to deploy your Spring Application as a WAR then you should not use the Spring Boot Adapter and use the dedicated adapter for the application server or servlet container you are using. Your Spring Boot should also contain a web.xml file.
|
2.1.7. Tomcat 7, 8 and 9 Adapters
To be able to secure WAR apps deployed on Tomcat 7, 8 and 9 you must install the Keycloak Tomcat 7 adapter or Keycloak Tomcat adapter into your Tomcat installation.
You then have to provide some extra configuration in each WAR you deploy to Tomcat.
Let’s go over these steps.
Adapter Installation
Adapters are no longer included with the appliance or war distribution.
Each adapter is a separate download on the Keycloak download site.
They are also available as a maven artifact.
You must unzip the adapter distro into Tomcat’s lib/
directory.
Including adapter’s jars within your WEB-INF/lib directory will not work! The Keycloak adapter is implemented as a Valve and valve code must reside in Tomcat’s main lib/ directory.
Install on Tomcat 7:
$ cd $TOMCAT_HOME/lib
$ unzip keycloak-tomcat7-adapter-dist.zip
Install on Tomcat 8 or 9:
$ cd $TOMCAT_HOME/lib
$ unzip keycloak-tomcat-adapter-dist.zip
Required Per WAR Configuration
This section describes how to secure a WAR directly by adding config and editing files within your WAR package.
The first thing you must do is create a META-INF/context.xml
file in your WAR package.
This is a Tomcat specific config file and you must define a Keycloak specific Valve.
<Context path="/your-context-path">
<Valve className="org.keycloak.adapters.tomcat.KeycloakAuthenticatorValve"/>
</Context>
Next you must create a keycloak.json
adapter config file within the WEB-INF
directory of your WAR.
The format of this config file is described in the Java adapter configuration
Finally you must specify both a login-config
and use standard servlet security to specify role-base constraints on your URLs.
Here’s an example:
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<module-name>customer-portal</module-name>
<security-constraint>
<web-resource-collection>
<web-resource-name>Customers</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>user</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>this is ignored currently</realm-name>
</login-config>
<security-role>
<role-name>admin</role-name>
</security-role>
<security-role>
<role-name>user</role-name>
</security-role>
</web-app>
2.1.8. Jetty 9.x Adapters
Keycloak has a separate adapter for Jetty 9.2.x, Jetty 9.3.x and Jetty 9.4.x that you will have to install into your Jetty installation.
You then have to provide some extra configuration in each WAR you deploy to Jetty.
Let’s go over these steps.
Adapter Installation
Adapters are no longer included with the appliance or war distribution. Each adapter is a separate download on the Keycloak download site.
They are also available as a maven artifact.
You must unzip the Jetty 9.x distro into Jetty 9.x’s base directory.
Including adapter’s jars within your WEB-INF/lib directory will not work!
In the example below, the Jetty base is named your-base
:
$ cd your-base
$ unzip keycloak-jetty93-adapter-dist-2.5.0.Final.zip
Next, you will have to enable the keycloak
module for your Jetty base:
$ java -jar $JETTY_HOME/start.jar --add-to-startd=keycloak
Required Per WAR Configuration
This section describes how to secure a WAR directly by adding config and editing files within your WAR package.
The first thing you must do is create a WEB-INF/jetty-web.xml
file in your WAR package.
This is a Jetty specific config file and you must define a Keycloak specific authenticator within it.
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
<Get name="securityHandler">
<Set name="authenticator">
<New class="org.keycloak.adapters.jetty.KeycloakJettyAuthenticator">
</New>
</Set>
</Get>
</Configure>
Next you must create a keycloak.json
adapter config file within the WEB-INF
directory of your WAR.
The format of this config file is described in the Java adapter configuration section.
The Jetty 9.x adapter will not be able to find the keycloak.json file.You will have to define all adapter settings within the jetty-web.xml file as described below.
|
Instead of using keycloak.json, you can define everything within the jetty-web.xml
.
You’ll just have to figure out how the json settings match to the org.keycloak.representations.adapters.config.AdapterConfig
class.
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
<Get name="securityHandler">
<Set name="authenticator">
<New class="org.keycloak.adapters.jetty.KeycloakJettyAuthenticator">
<Set name="adapterConfig">
<New class="org.keycloak.representations.adapters.config.AdapterConfig">
<Set name="realm">tomcat</Set>
<Set name="resource">customer-portal</Set>
<Set name="authServerUrl">http://localhost:8081/auth</Set>
<Set name="sslRequired">external</Set>
<Set name="credentials">
<Map>
<Entry>
<Item>secret</Item>
<Item>password</Item>
</Entry>
</Map>
</Set>
</New>
</Set>
</New>
</Set>
</Get>
</Configure>
You do not have to crack open your WAR to secure it with keycloak.
Instead create the jetty-web.xml file in your webapps directory with the name of yourwar.xml.
Jetty should pick it up.
In this mode, you’ll have to declare keycloak.json configuration directly within the xml file.
Finally you must specify both a login-config
and use standard servlet security to specify role-base constraints on your URLs.
Here’s an example:
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<module-name>customer-portal</module-name>
<security-constraint>
<web-resource-collection>
<web-resource-name>Customers</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>user</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>this is ignored currently</realm-name>
</login-config>
<security-role>
<role-name>admin</role-name>
</security-role>
<security-role>
<role-name>user</role-name>
</security-role>
</web-app>
2.1.9. Spring Security Adapter
To secure an application with Spring Security and Keycloak, add this adapter as a dependency to your project.
You then have to provide some extra beans in your Spring Security configuration file and add the Keycloak security filter to your pipeline.
Unlike the other Keycloak Adapters, you should not configure your security in web.xml.
However, keycloak.json is still required.
In order for Single Sign Out work properly you have to define a session listener.
The session listener can be defined:
-
in web.xml (for pure Spring Security environments):
<listener> <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class> </listener>
-
as a Spring bean (in Spring Boot environments using Spring Security adapter)
@Bean public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() { return new ServletListenerRegistrationBean<HttpSessionEventPublisher>(new HttpSessionEventPublisher()); }
Adapter Installation
Add Keycloak Spring Security adapter as a dependency to your Maven POM or Gradle build.
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-security-adapter</artifactId>
<version>11.0.3</version>
</dependency>
Spring Security Configuration
The Keycloak Spring Security adapter takes advantage of Spring Security’s flexible security configuration syntax.
Java Configuration
Keycloak provides a KeycloakWebSecurityConfigurerAdapter as a convenient base class for creating a WebSecurityConfigurer instance.
The implementation allows customization by overriding methods.
While its use is not required, it greatly simplifies your security context configuration.
@KeycloakConfiguration
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter
{
/**
* Registers the KeycloakAuthenticationProvider with the authentication manager.
*/
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(keycloakAuthenticationProvider());
}
/**
* Defines the session authentication strategy.
*/
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
@Override
protected void configure(HttpSecurity http) throws Exception
{
super.configure(http);
http
.authorizeRequests()
.antMatchers("/customers*").hasRole("USER")
.antMatchers("/admin*").hasRole("ADMIN")
.anyRequest().permitAll();
}
}
You must provide a session authentication strategy bean which should be of type RegisterSessionAuthenticationStrategy
for public or confidential applications and NullAuthenticatedSessionStrategy
for bearer-only applications.
Spring Security’s SessionFixationProtectionStrategy
is currently not supported because it changes the session identifier after login via Keycloak.
If the session identifier changes, universal log out will not work because Keycloak is unaware of the new session identifier.
The @KeycloakConfiguration annotation is a metadata annotion that defines all annotations that are needed to integrateKeycloak in Spring Security. If you have a complexe Spring Security setup you can simply have a look ath the annotations of the @KeycloakConfiguration annotation and create your own custom meta annotation or just use specific Spring annotationsfor the Keycloak adapter. |
XML Configuration
While Spring Security’s XML namespace simplifies configuration, customizing the configuration can be a bit verbose.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<context:component-scan base-package="org.keycloak.adapters.springsecurity" />
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="keycloakAuthenticationProvider" />
</security:authentication-manager>
<bean id="adapterDeploymentContext" class="org.keycloak.adapters.springsecurity.AdapterDeploymentContextFactoryBean">
<constructor-arg value="/WEB-INF/keycloak.json" />
</bean>
<bean id="keycloakAuthenticationEntryPoint" class="org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationEntryPoint" />
<bean id="keycloakAuthenticationProvider" class="org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider" />
<bean id="keycloakPreAuthActionsFilter" class="org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter" />
<bean id="keycloakAuthenticationProcessingFilter" class="org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter">
<constructor-arg name="authenticationManager" ref="authenticationManager" />
</bean>
<bean id="keycloakLogoutHandler" class="org.keycloak.adapters.springsecurity.authentication.KeycloakLogoutHandler">
<constructor-arg ref="adapterDeploymentContext" />
</bean>
<bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
<constructor-arg name="logoutSuccessUrl" value="/" />
<constructor-arg name="handlers">
<list>
<ref bean="keycloakLogoutHandler" />
<bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" />
</list>
</constructor-arg>
<property name="logoutRequestMatcher">
<bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
<constructor-arg name="pattern" value="/sso/logout**" />
<constructor-arg name="httpMethod" value="GET" />
</bean>
</property>
</bean>
<security:http auto-config="false" entry-point-ref="keycloakAuthenticationEntryPoint">
<security:custom-filter ref="keycloakPreAuthActionsFilter" before="LOGOUT_FILTER" />
<security:custom-filter ref="keycloakAuthenticationProcessingFilter" before="FORM_LOGIN_FILTER" />
<security:intercept-url pattern="/customers**" access="ROLE_USER" />
<security:intercept-url pattern="/admin**" access="ROLE_ADMIN" />
<security:custom-filter ref="logoutFilter" position="LOGOUT_FILTER" />
</security:http>
</beans>
Multi Tenancy
The Keycloak Spring Security adapter also supports multi tenancy.
Instead of injecting AdapterDeploymentContextFactoryBean
with the path to keycloak.json
you can inject an implementation of the KeycloakConfigResolver
interface.
More details on how to implement the KeycloakConfigResolver
can be found in Multi Tenancy.
Naming Security Roles
Spring Security, when using role-based authentication, requires that role names start with ROLE_
.
For example, an administrator role must be declared in Keycloak as ROLE_ADMIN
or similar, not simply ADMIN
.
The class org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider
supports an optional org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper
which can be used to map roles coming from Keycloak to roles recognized by Spring Security.
Use, for example, org.springframework.security.core.authority.mapping.SimpleAuthorityMapper
to insert the ROLE_
prefix and convert the role name to upper case.
The class is part of Spring Security Core module.
Client to Client Support
To simplify communication between clients, Keycloak provides an extension of Spring’s RestTemplate
that handles bearer token authentication for you.
To enable this feature your security configuration must add the KeycloakRestTemplate
bean.
Note that it must be scoped as a prototype to function correctly.
For Java configuration:
@Configuration
@EnableWebSecurity
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
...
@Autowired
public KeycloakClientRequestFactory keycloakClientRequestFactory;
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public KeycloakRestTemplate keycloakRestTemplate() {
return new KeycloakRestTemplate(keycloakClientRequestFactory);
}
...
}
For XML configuration:
<bean id="keycloakRestTemplate" class="org.keycloak.adapters.springsecurity.client.KeycloakRestTemplate" scope="prototype">
<constructor-arg name="factory" ref="keycloakClientRequestFactory" />
</bean>
Your application code can then use KeycloakRestTemplate
any time it needs to make a call to another client.
For example:
@Service
public class RemoteProductService implements ProductService {
@Autowired
private KeycloakRestTemplate template;
private String endpoint;
@Override
public List<String> getProducts() {
ResponseEntity<String[]> response = template.getForEntity(endpoint, String[].class);
return Arrays.asList(response.getBody());
}
}
Spring Boot Integration
The Spring Boot and the Spring Security adapters can be combined.
If you are using the Keycloak Spring Boot Starter to make use of the Spring Security adapter you just need to add the Spring Security starter :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Using Spring Boot Configuration
By Default, the Spring Security Adapter looks for a keycloak.json
configuration file. You can make sure it looks at the configuration provided by the Spring Boot Adapter by adding this bean :
@Bean
public KeycloakConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
Avoid double bean registration
Spring Boot attempts to eagerly register filter beans with the web application context.
Therefore, when running the Keycloak Spring Security adapter in a Spring Boot environment, it may be necessary to add FilterRegistrationBean
s to your security configuration to prevent the Keycloak filters from being registered twice.
Spring Boot 2.1 also disables spring.main.allow-bean-definition-overriding
by default. This can mean that an BeanDefinitionOverrideException
will be encountered if a Configuration
class extending KeycloakWebSecurityConfigurerAdapter
registers a bean that is already detected by a @ComponentScan
. This can be avoided by overriding the registration to use the Boot-specific @ConditionalOnMissingBean
annotation, as with HttpSessionManager
below.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter
{
...
@Bean
public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean(
KeycloakAuthenticationProcessingFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
@Bean
public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean(
KeycloakPreAuthActionsFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
@Bean
public FilterRegistrationBean keycloakAuthenticatedActionsFilterBean(
KeycloakAuthenticatedActionsFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
@Bean
public FilterRegistrationBean keycloakSecurityContextRequestFilterBean(
KeycloakSecurityContextRequestFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
@Bean
@Override
@ConditionalOnMissingBean(HttpSessionManager.class)
protected HttpSessionManager httpSessionManager() {
return new HttpSessionManager();
}
...
}
2.1.10. Java Servlet Filter Adapter
If you are deploying your Java Servlet application on a platform where there is no Keycloak adapter you opt to use the servlet filter adapter.
This adapter works a bit differently than the other adapters. You do not define security constraints in web.xml.
Instead you define a filter mapping using the Keycloak servlet filter adapter to secure the url patterns you want to secure.
Backchannel logout works a bit differently than the standard adapters. Instead of invalidating the HTTP session it marks the session id as logged out. There’s no standard way to invalidate an HTTP session based on a session id. |
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<module-name>application</module-name>
<filter>
<filter-name>Keycloak Filter</filter-name>
<filter-class>org.keycloak.adapters.servlet.KeycloakOIDCFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>Keycloak Filter</filter-name>
<url-pattern>/keycloak/*</url-pattern>
<url-pattern>/protected/*</url-pattern>
</filter-mapping>
</web-app>
In the snippet above there are two url-patterns.
/protected/* are the files we want protected, while the /keycloak/* url-pattern handles callbacks from the Keycloak server.
If you need to exclude some paths beneath the configured url-patterns
you can use the Filter init-param keycloak.config.skipPattern
to configure
a regular expression that describes a path-pattern for which the keycloak filter should immediately delegate to the filter-chain.
By default no skipPattern is configured.
Patterns are matched against the requestURI
without the context-path
. Given the context-path /myapp
a request for /myapp/index.html
will be matched with /index.html
against the skip pattern.
<init-param>
<param-name>keycloak.config.skipPattern</param-name>
<param-value>^/(path1|path2|path3).*</param-value>
</init-param>
Note that you should configure your client in the Keycloak Admin Console with an Admin URL that points to a secured section covered by the filter’s url-pattern.
The Admin URL will make callbacks to the Admin URL to do things like backchannel logout.
So, the Admin URL in this example should be http[s]://hostname/{context-root}/keycloak
.
The Keycloak filter has the same configuration parameters as the other adapters except you must define them as filter init params instead of context params.
To use this filter, include this maven artifact in your WAR poms:
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-servlet-filter-adapter</artifactId>
<version>11.0.3</version>
</dependency>
Using on OSGi
The servlet filter adapter is packaged as an OSGi bundle, and thus is usable in a generic OSGi environment (R6 and above) with HTTP Service and HTTP Whiteboard.
Installation
The adapter and its dependencies are distributed as Maven artifacts, so you’ll need either working Internet connection to access Maven Central, or have the artifacts cached in your local Maven repo.
If you are using Apache Karaf, you can simply install a feature from the Keycloak feature repo:
karaf@root()> feature:repo-add mvn:org.keycloak/keycloak-osgi-features/11.0.3/xml/features
karaf@root()> feature:install keycloak-servlet-filter-adapter
For other OSGi runtimes, please refer to the runtime documentation on how to install the adapter bundle and its dependencies.
If your OSGi platform is Apache Karaf with Pax Web, you should consider using JBoss Fuse 6 or JBoss Fuse 7 adapters instead. |
Configuration
First, the adapter needs to be registered as a servlet filter with the OSGi HTTP Service. The most common ways to do this are programmatic (e.g. via bundle activator) and declarative (using OSGi annotations).
We recommend using the latter since it simplifies the process of dynamically registering and un-registering the filter:
package mypackage;
import javax.servlet.Filter;
import org.keycloak.adapters.servlet.KeycloakOIDCFilter;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.http.whiteboard.HttpWhiteboardConstants;
@Component(
immediate = true,
service = Filter.class,
property = {
KeycloakOIDCFilter.CONFIG_FILE_PARAM + "=" + "keycloak.json",
HttpWhiteboardConstants.HTTP_WHITEBOARD_FILTER_PATTERN + "=" +"/*",
HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT + "=" + "(osgi.http.whiteboard.context.name=mycontext)"
}
)
public class KeycloakFilter extends KeycloakOIDCFilter {
//
}
The above snippet uses OSGi declarative service specification to expose the filter as an OSGI service under javax.servlet.Filter
class.
Once the class is published in the OSGi service registry, it is going to be picked up by OSGi HTTP Service implementation and used for filtering requests for the specified servlet context. This will trigger Keycloak adapter for every request that matches servlet context path + filter path.
Since the component is put under the control of OSGi Configuration Admin Service, it’s properties can be configured dynamically.
To do that, either create a mypackage.KeycloakFilter.cfg
file under the standard config location for your OSGi runtime:
keycloak.config.file = /path/to/keycloak.json
osgi.http.whiteboard.filter.pattern = /secure/*
or use interactive console, if your runtime allows for that:
karaf@root()> config:edit mypackage.KeycloakFilter
karaf@root()> config:property-set keycloak.config.file '${karaf.etc}/keycloak.json'
karaf@root()> config:update
If you need more control, like e.g. providing custom KeycloakConfigResolver
to implement multi tenancy, you can register the filter programmatically:
public class Activator implements BundleActivator {
private ServiceRegistration registration;
public void start(BundleContext context) throws Exception {
Hashtable props = new Hashtable();
props.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_FILTER_PATTERN, "/secure/*");
props.put(KeycloakOIDCFilter.CONFIG_RESOLVER_PARAM, new MyConfigResolver());
this.registration = context.registerService(Filter.class.getName(), new KeycloakOIDCFilter(), props);
}
public void stop(BundleContext context) throws Exception {
this.registration.unregister();
}
}
2.1.11. JAAS plugin
It’s generally not needed to use JAAS for most of the applications, especially if they are HTTP based, and you should most likely choose one of our other adapters.
However, some applications and systems may still rely on pure legacy JAAS solution.
Keycloak provides two login modules to help in these situations.
The provided login modules are:
- org.keycloak.adapters.jaas.DirectAccessGrantsLoginModule
-
This login module allows to authenticate with username/password from Keycloak.
It’s using Resource Owner Password Credentials flow to validate if the provided
username/password is valid. It’s useful for non-web based systems, which need to rely on JAAS and want to use Keycloak, but can’t use the standard browser
based flows due to their non-web nature. Example of such application could be messaging or SSH. - org.keycloak.adapters.jaas.BearerTokenLoginModule
-
This login module allows to authenticate with Keycloak access token passed to it through CallbackHandler as password.
It may be useful for example in case, when you have Keycloak access token from standard based authentication flow and your web application then
needs to talk to external non-web based system, which rely on JAAS. For example a messaging system.
Both modules use the following configuration properties:
- keycloak-config-file
-
The location of the
keycloak.json
configuration file. The configuration file can either be located on the filesystem or on the classpath. If it’s located
on the classpath you need to prefix the location withclasspath:
(for exampleclasspath:/path/keycloak.json
).
This is REQUIRED. role-principal-class
-
Configure alternative class for Role principals attached to JAAS Subject.
Default value isorg.keycloak.adapters.jaas.RolePrincipal
. Note: The class is required to have a constructor with a singleString
argument. scope
-
This option is only applicable to the
DirectAccessGrantsLoginModule
. The specified value will be used as the OAuth2scope
parameter in the Resource Owner Password Credentials Grant request.
2.1.12. CLI / Desktop Applications
Keycloak supports securing desktop
(e.g. Swing, JavaFX) or CLI applications via the
KeycloakInstalled
adapter by performing the authentication step via the system browser.
The KeycloakInstalled
adapter supports a desktop
and a manual
variant. The desktop variant uses the system browser
to gather the user credentials. The manual variant
reads the user credentials from STDIN
.
How it works
To authenticate a user with the desktop
variant the KeycloakInstalled
adapter opens a desktop browser window where a user uses the regular Keycloak
login pages to login when the loginDesktop()
method is called on the KeycloakInstalled
object.
The login page URL is opened with redirect parameter
that points to a local ServerSocket
listening on a free ephemeral port
on localhost
which is started by the adapter.
After a succesful login the KeycloakInstalled
receives the authorization code
from the incoming HTTP request and performs the authorization code flow.
Once the code to token exchange is completed the ServerSocket
is shutdown.
If the user already has an active Keycloak session then the login form is not shown but the code to token exchange is continued, which enables a smooth Web based SSO experience. |
The client eventually receives the tokens (access_token, refresh_token,
id_token) which can then be used to call backend services.
The KeycloakInstalled
adapter provides support for renewal of stale tokens.
Adapter Installation
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-installed-adapter</artifactId>
<version>11.0.3</version>
</dependency>
Client Configuration
The application needs to be configured as a public
OpenID Connect client with
Standard Flow Enabled
and http://localhost as an allowed Valid Redirect URI
.
The KeycloakInstalled adapter supports the PKCE [RFC 7636] mechanism to provide additional protection duringcode to token exchanges in the OIDC protocol. PKCE can be enabled with the "enable-pkce": true settingin the adapter configuration. Enabling PKCE is highly recommended, to avoid code injection and code replay attacks. |
Usage
The KeycloakInstalled
adapter reads it’s configuration from
META-INF/keycloak.json
on the classpath. Custom configurations
can be supplied with an InputStream
or a KeycloakDeployment
through the KeycloakInstalled
constructor.
In the example below, the client configuration for desktop-app
uses the following keycloak.json
:
{
"realm": "desktop-app-auth",
"auth-server-url": "http://localhost:8081/auth",
"ssl-required": "external",
"resource": "desktop-app",
"public-client": true,
"use-resource-role-mappings": true,
"enable-pkce": true
}
the following sketch demonstrates working with the KeycloakInstalled
adapter:
// reads the configuration from classpath: META-INF/keycloak.json
KeycloakInstalled keycloak = new KeycloakInstalled();
// opens desktop browser
keycloak.loginDesktop();
AccessToken token = keycloak.getToken();
// use token to send backend request
// ensure token is valid for at least 30 seconds
long minValidity = 30L;
String tokenString = keycloak.getTokenString(minValidity, TimeUnit.SECONDS);
// when you want to logout the user.
keycloak.logout();
The KeycloakInstalled class supports customization of the http responses returned bylogin / logout requests via the loginResponseWriter and logoutResponseWriter attributes.
|
Example
The following provides an example for the configuration mentioned above.
import java.util.Locale;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.keycloak.adapters.installed.KeycloakInstalled;
import org.keycloak.representations.AccessToken;
public class DesktopApp {
public static void main(String[] args) throws Exception {
KeycloakInstalled keycloak = new KeycloakInstalled();
keycloak.setLocale(Locale.ENGLISH);
keycloak.loginDesktop();
AccessToken token = keycloak.getToken();
Executors.newSingleThreadExecutor().submit(() -> {
System.out.println("Logged in...");
System.out.println("Token: " + token.getSubject());
System.out.println("Username: " + token.getPreferredUsername());
try {
System.out.println("AccessToken: " + keycloak.getTokenString());
} catch (Exception ex) {
ex.printStackTrace();
}
int timeoutSeconds = 20;
System.out.printf("Logging out in...%d Seconds%n", timeoutSeconds);
try {
TimeUnit.SECONDS.sleep(timeoutSeconds);
} catch (Exception e) {
e.printStackTrace();
}
try {
keycloak.logout();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Exiting...");
System.exit(0);
});
}
}
2.1.13. Security Context
The KeycloakSecurityContext
interface is available if you need to access to the tokens directly. This could be useful if you want to retrieve additional
details from the token (such as user profile information) or you want to invoke a RESTful service that is protected by Keycloak.
In servlet environments it is available in secured invocations as an attribute in HttpServletRequest:
httpServletRequest
.getAttribute(KeycloakSecurityContext.class.getName());
Or, it is available in insecured requests in the HttpSession:
httpServletRequest.getSession()
.getAttribute(KeycloakSecurityContext.class.getName());
2.1.14. Error Handling
Keycloak has some error handling facilities for servlet based client adapters.
When an error is encountered in authentication, Keycloak will call HttpServletResponse.sendError()
.
You can set up an error-page within your web.xml
file to handle the error however you want.
Keycloak can throw 400, 401, 403, and 500 errors.
<error-page>
<error-code>403</error-code>
<location>/ErrorHandler</location>
</error-page>
Keycloak also sets a HttpServletRequest
attribute that you can retrieve.
The attribute name is org.keycloak.adapters.spi.AuthenticationError
, which should be casted to org.keycloak.adapters.OIDCAuthenticationError
.
For example:
import org.keycloak.adapters.OIDCAuthenticationError;
import org.keycloak.adapters.OIDCAuthenticationError.Reason;
...
OIDCAuthenticationError error = (OIDCAuthenticationError) httpServletRequest
.getAttribute('org.keycloak.adapters.spi.AuthenticationError');
Reason reason = error.getReason();
System.out.println(reason.name());
2.1.15. Logout
You can log out of a web application in multiple ways.
For Java EE servlet containers, you can call HttpServletRequest.logout()
. For other browser applications, you can redirect the browser to
http://auth-server/auth/realms/{realm-name}/protocol/openid-connect/logout?redirect_uri=encodedRedirectUri
, which logs you out if you have an SSO session with your browser.
When using the HttpServletRequest.logout()
option the adapter executes a back-channel POST call against the Keycloak server passing the refresh token.
If the method is executed from an unprotected page (a page that does not check for a valid token) the refresh token can be unavailable and, in that case,
the adapter skips the call. For this reason, using a protected page to execute HttpServletRequest.logout()
is recommended so that current tokens are always
taken into account and an interaction with the Keycloak server is performed if needed.
If you want to avoid logging out of an external identity provider as part of the logout process, you can supply the parameter initiating_idp
, with the value being
the identity (alias) of the identity provider in question. This is useful when the logout endpoint is invoked as part of single logout initiated by the external identity provider.
2.1.16. Parameters Forwarding
The Keycloak initial authorization endpoint request has support for various parameters. Most of the parameters are described in
OIDC specification. Some parameters are added automatically by the adapter based
on the adapter configuration. However, there are also a few parameters that can be added on a per-invocation basis. When you open the secured application URI,
the particular parameter will be forwarded to the Keycloak authorization endpoint.
For example, if you request an offline token, then you can open the secured application URI with the scope
parameter like:
http://myappserver/mysecuredapp?scope=offline_access
and the parameter scope=offline_access
will be automatically forwarded to the Keycloak authorization endpoint.
The supported parameters are:
-
scope — Use a space-delimited list of scopes. A space-delimited list typically references Client scopes
defined on particular client. Note that the scopeopenid
will be always be added to the list of scopes by the adapter. For example, if you
enter the scope optionsaddress phone
, then the request to Keycloak will contain the scope parameterscope=openid address phone
. -
prompt — Keycloak supports these settings:
-
login
— SSO will be ignored and the Keycloak login page will be always shown, even if the user is already authenticated -
consent
— Applicable only for the clients withConsent Required
. If it is used, the Consent page will always be displayed,
even if the user previously granted consent to this client. -
none
— The login page will never be shown; instead the user will be redirected to the application, with an error if the user
is not yet authenticated. This setting allows you to create a filter/interceptor on the application side and show a custom error page
to the user. See more details in the specification.
-
-
max_age — Used only if a user is already authenticated. Specifies maximum permitted time for the authentication to persist, measured
from when the user authenticated. If user is authenticated longer thanmaxAge
, the SSO is ignored and he must re-authenticate. -
login_hint — Used to pre-fill the username/email field on the login form.
-
kc_idp_hint — Used to tell Keycloak to skip showing login page and automatically redirect to specified identity provider instead.
More info in the Identity Provider documentation.
Most of the parameters are described in the OIDC specification.
The only exception is parameter kc_idp_hint
, which is specific to Keycloak and contains the name of the identity provider to automatically use.
For more information see the Identity Brokering
section in Server Administration Guide.
If you open the URL using the attached parameters, the adapter will not redirect you to Keycloak if you are already authenticated in the application. For example, opening http://myappserver/mysecuredapp?prompt=login will not automatically redirect you to the Keycloak login page if you are already authenticated to the application mysecredapp . This behavior may be changed in the future.
|
2.1.17. Client Authentication
When a confidential OIDC client needs to send a backchannel request (for example, to exchange code for the token, or to refresh the token) it needs to authenticate against the Keycloak server. By default, there are three ways to authenticate the client: client ID and client secret, client authentication with signed JWT, or client authentication with signed JWT using client secret.
Client ID and Client Secret
This is the traditional method described in the OAuth2 specification. The client has a secret, which needs to be known to both the adapter (application) and the Keycloak server.
You can generate the secret for a particular client in the Keycloak administration console, and then paste this secret into the keycloak.json
file on the application side:
"credentials": {
"secret": "19666a4f-32dd-4049-b082-684c74115f28"
}
Client Authentication with Signed JWT
This is based on the RFC7523 specification. It works this way:
-
The client must have the private key and certificate. For Keycloak this is available through the traditional
keystore
file, which is either available on the client application’s classpath or somewhere on the file system. -
Once the client application is started, it allows to download its public key in JWKS format using a URL such as http://myhost.com/myapp/k_jwks, assuming that http://myhost.com/myapp is the base URL of your client application. This URL can be used by Keycloak (see below).
-
During authentication, the client generates a JWT token and signs it with its private key and sends it to Keycloak in
the particular backchannel request (for example, code-to-token request) in theclient_assertion
parameter. -
Keycloak must have the public key or certificate of the client so that it can verify the signature on JWT. In Keycloak you need to configure client credentials for your client. First you need to choose
Signed JWT
as the method of authenticating your client in the tabCredentials
in administration console.
Then you can choose to either:-
Configure the JWKS URL where Keycloak can download the client’s public keys. This can be a URL such as http://myhost.com/myapp/k_jwks (see details above). This option is the most flexible, since the client can rotate its keys anytime and Keycloak then always downloads new keys when needed without needing to change the configuration. More accurately, Keycloak downloads new keys when it sees the token signed by an unknown
kid
(Key ID). -
Upload the client’s public key or certificate, either in PEM format, in JWK format, or from the keystore. With this option, the public key is hardcoded and must be changed when the client generates a new key pair.
You can even generate your own keystore from the Keycloak admininstration console if you don’t have your own available.
For more details on how to set up the Keycloak administration console see Server Administration Guide.
-
For set up on the adapter side you need to have something like this in your keycloak.json
file:
"credentials": {
"jwt": {
"client-keystore-file": "classpath:keystore-client.jks",
"client-keystore-type": "JKS",
"client-keystore-password": "storepass",
"client-key-password": "keypass",
"client-key-alias": "clientkey",
"token-expiration": 10
}
}
With this configuration, the keystore file keystore-client.jks
must be available on classpath in your WAR. If you do not use the prefix classpath:
you can point to any file on the file system where the client application is running.
For inspiration, you can take a look at the examples distribution into the main demo example into the product-portal
application.
Client Authentication with Signed JWT using Client Secret
This is the same as Client Authentication with Signed JWT except for using the client secret instead of the private key and certificate.
The client has a secret, which needs to be known to both the adapter (application) and the Keycloak server. You need to choose Signed JWT with Client Secret
as the method of authenticating your client in the tab Credentials
in administration console, and then paste this secret into the keycloak.json
file on the application side:
"credentials": {
"secret-jwt": {
"secret": "19666a4f-32dd-4049-b082-684c74115f28",
"algorithm": "HS512"
}
}
The «algorithm» field specifies the algorithm for Signed JWT using Client Secret. It needs to be one of the following values : HS256, HS384, and HS512. For details, please refer to JSON Web Algorithms (JWA).
This «algorithm» field is optional so that HS256 is applied automatically if the «algorithm» field does not exist on the keycloak.json
file.
Add Your Own Client Authentication Method
You can add your own client authentication method as well. You will need to implement both client-side and server-side providers. For more details see the Authentication SPI
section in Server Developer Guide.
2.1.18. Multi Tenancy
Multi Tenancy, in our context, means that a single target application (WAR) can be secured with multiple Keycloak realms. The realms can be located
on the same Keycloak instance or on different instances.
In practice, this means that the application needs to have multiple keycloak.json
adapter configuration files.
You could have multiple instances of your WAR with different adapter configuration files deployed to different context-paths. However, this may be inconvenient
and you may also want to select the realm based on something else than context-path.
Keycloak makes it possible to have a custom config resolver so you can choose what adapter config is used for each request.
To achieve this first you need to create an implementation of org.keycloak.adapters.KeycloakConfigResolver
. For example:
package example;
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
public class PathBasedKeycloakConfigResolver implements KeycloakConfigResolver {
@Override
public KeycloakDeployment resolve(OIDCHttpFacade.Request request) {
if (path.startsWith("alternative")) {
KeycloakDeployment deployment = cache.get(realm);
if (null == deployment) {
InputStream is = getClass().getResourceAsStream("/tenant1-keycloak.json");
return KeycloakDeploymentBuilder.build(is);
}
} else {
InputStream is = getClass().getResourceAsStream("/default-keycloak.json");
return KeycloakDeploymentBuilder.build(is);
}
}
}
You also need to configure which KeycloakConfigResolver
implementation to use with the keycloak.config.resolver
context-param in your web.xml
:
<web-app>
...
<context-param>
<param-name>keycloak.config.resolver</param-name>
<param-value>example.PathBasedKeycloakConfigResolver</param-value>
</context-param>
</web-app>
2.1.19. Application Clustering
This chapter is related to supporting clustered applications deployed to JBoss EAP, WildFly and JBoss AS.
There are a few options available depending on whether your application is:
-
Stateless or stateful
-
Distributable (replicated http session) or non-distributable
-
Relying on sticky sessions provided by load balancer
-
Hosted on same domain as Keycloak
Dealing with clustering is not quite as simple as for a regular application. Mainly due to the fact that both the browser and the server-side application
sends requests to Keycloak, so it’s not as simple as enabling sticky sessions on your load balancer.
Stateless token store
By default, the web application secured by Keycloak uses the HTTP session to store security context. This means that you either have to
enable sticky sessions or replicate the HTTP session.
As an alternative to storing the security context in the HTTP session the adapter can be configured to store this in a cookie instead. This is useful if you want
to make your application stateless or if you don’t want to store the security context in the HTTP session.
To use the cookie store for saving the security context, edit your applications WEB-INF/keycloak.json
and add:
The default value for token-store is session , which stores the security context in the HTTP session.
|
One limitation of using the cookie store is that the whole security context is passed in the cookie for every HTTP request. This may impact performance.
Another small limitation is limited support for Single-Sign Out. It works without issues if you init servlet logout (HttpServletRequest.logout) from the
application itself as the adapter will delete the KEYCLOAK_ADAPTER_STATE cookie. However, back-channel logout initialized from a different application isn’t
propagated by Keycloak to applications using cookie store. Hence it’s recommended to use a short value for the access token timeout (for example 1 minute).
Some load balancers do not allow any configuration of the sticky session cookie name or contents, such as Amazon ALB. For these, it is recommended to set the shouldAttachRoute option to false .
|
Relative URI optimization
In deployment scenarios where Keycloak and the application is hosted on the same domain (through a reverse proxy or load balancer) it can be
convenient to use relative URI options in your client configuration.
With relative URIs the URI is resolved as relative to the URL used to access Keycloak.
For example if the URL to your application is https://acme.org/myapp
and the URL to Keycloak is https://acme.org/auth
, then you can use
the redirect-uri /myapp
instead of https://acme.org/myapp
.
Admin URL configuration
Admin URL for a particular client can be configured in the Keycloak Administration Console.
It’s used by the Keycloak server to send backend requests to the application for various tasks, like logout users or push revocation policies.
For example the way backchannel logout works is:
-
User sends logout request from one application
-
The application sends logout request to Keycloak
-
The Keycloak server invalidates the user session
-
The Keycloak server then sends a backchannel request to application with an admin url that are associated with the session
-
When an application receives the logout request it invalidates the corresponding HTTP session
If admin URL contains ${application.session.host}
it will be replaced with the URL to the node associated with the HTTP session.
Registration of application nodes
The previous section describes how Keycloak can send logout request to node associated with a specific HTTP session.
However, in some cases admin may want to propagate admin tasks to all registered cluster nodes, not just one of them.
For example to push a new not before policy to the application or to logout all users from the application.
In this case Keycloak needs to be aware of all application cluster nodes, so it can send the event to all of them.
To achieve this, we support auto-discovery mechanism:
-
When a new application node joins the cluster, it sends a registration request to the Keycloak server
-
The request may be re-sent to Keycloak in configured periodic intervals
-
If the Keycloak server doesn’t receive a re-registration request within a specified timeout then it automatically unregisters the specific node
-
The node is also unregistered in Keycloak when it sends an unregistration request, which is usually during node shutdown or application undeployment.
This may not work properly for forced shutdown when undeployment listeners are not invoked, which results in the need for automatic unregistration
Sending startup registrations and periodic re-registration is disabled by default as it’s only required for some clustered applications.
To enable the feature edit the WEB-INF/keycloak.json
file for your application and add:
"register-node-at-startup": true,
"register-node-period": 600,
This means the adapter will send the registration request on startup and re-register every 10 minutes.
In the Keycloak Administration Console you can specify the maximum node re-registration timeout (should be larger than register-node-period from
the adapter configuration). You can also manually add and remove cluster nodes in through the Adminstration Console, which is useful if you don’t want to rely
on the automatic registration feature or if you want to remove stale application nodes in the event your not using the automatic unregistration feature.
Refresh token in each request
By default the application adapter will only refresh the access token when it’s expired. However, you can also configure the adapter to refresh the token on every
request. This may have a performance impact as your application will send more requests to the Keycloak server.
To enable the feature edit the WEB-INF/keycloak.json
file for your application and add:
"always-refresh-token": true
This may have a significant impact on performance. Only enable this feature if you can’t rely on backchannel messages to propagate logout and not before policies. Another thing to consider is that by default access tokens has a short expiration so even if logout is not propagated the token will expire within minutes of the logout. |
2.2. JavaScript Adapter
Keycloak comes with a client-side JavaScript library that can be used to secure HTML5/JavaScript applications. The JavaScript adapter has built-in support for Cordova applications.
The library can be retrieved directly from the Keycloak server at /auth/js/keycloak.js
and is also distributed as a ZIP archive.
A best practice is to load the JavaScript adapter directly from Keycloak Server as it will automatically be updated when you upgrade the server. If you copy the adapter to your web application instead, make sure you upgrade the adapter only after you have upgraded the server.
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. This makes it very important to make sure the redirect URIs you have configured for the client are correct and as specific as possible.
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
.
You also need to configure Valid Redirect URIs
and Web Origins
. Be as specific as possible as failing to do so may result in a security vulnerability.
Once the client is created click on the Installation
tab select Keycloak OIDC JSON
for Format Option
then click Download
. The downloaded
keycloak.json
file should be hosted on your web server at the same location as your HTML pages.
Alternatively, you can skip the configuration file and manually configure the adapter.
The following example shows how to initialize the JavaScript adapter:
<head>
<script src="keycloak.js"></script>
<script>
var keycloak = new Keycloak();
keycloak.init().then(function(authenticated) {
alert(authenticated ? 'authenticated' : 'not authenticated');
}).catch(function() {
alert('failed to initialize');
});
</script>
</head>
If the keycloak.json
file is in a different location you can specify it:
var keycloak = new Keycloak('http://localhost:8080/myapp/keycloak.json');
Alternatively, you can pass in a JavaScript object with the required configuration instead:
var keycloak = new Keycloak({
url: 'http://keycloak-server/auth',
realm: 'myrealm',
clientId: 'myapp'
});
By default to authenticate you need to call the login
function. However, there are two options available to make the adapter automatically authenticate. You
can pass login-required
or check-sso
to the init function. login-required
will authenticate the client if the user is logged-in to Keycloak
or display the login page if not. check-sso
will only authenticate the client if the user is already logged-in, if the user is not logged-in the browser will be
redirected back to the application and remain unauthenticated.
You can configure a silent check-sso
option.
With this feature enabled, your browser won’t do a full redirect to the Keycloak server and back to your application, but this action will be performed in a hidden iframe, so your application resources only need to be loaded and parsed once by the browser when the app is initialized and not again after the redirect back from Keycloak to your app.
This is particularly useful in case of SPAs (Single Page Applications).
To enable the silent check-sso
, you have to provide a silentCheckSsoRedirectUri
attribute in the init method.
This URI needs to be a valid endpoint in the application (and of course it must be configured as a valid redirect for the client in the Keycloak Administration Console):
keycloak.init({
onLoad: 'check-sso',
silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html'
})
The page at the silent check-sso redirect uri is loaded in the iframe after successfully checking your authentication state and retrieving the tokens from the Keycloak server.
It has no other task than sending the received tokens to the main application and should only look like this:
<html>
<body>
<script>
parent.postMessage(location.href, location.origin)
</script>
</body>
</html>
Please keep in mind that this page at the specified location must be provided by the application itself and is not part of the JavaScript adapter!
Silent check-sso functionality is limited in some modern browsers. Please see Modern Browsers with Tracking Protection Section.
|
To enable login-required
set onLoad
to login-required
and pass to the init method:
keycloak.init({
onLoad: 'login-required'
})
After the user is authenticated the application can make requests to RESTful services secured by Keycloak by including the bearer token in the
Authorization
header. For example:
var loadData = function () {
document.getElementById('username').innerText = keycloak.subject;
var url = 'http://localhost:8080/restful-service';
var req = new XMLHttpRequest();
req.open('GET', url, true);
req.setRequestHeader('Accept', 'application/json');
req.setRequestHeader('Authorization', 'Bearer ' + keycloak.token);
req.onreadystatechange = function () {
if (req.readyState == 4) {
if (req.status == 200) {
alert('Success');
} else if (req.status == 403) {
alert('Forbidden');
}
}
}
req.send();
};
One thing to keep in mind is that the access token by default has a short life expiration so you may need to refresh the access token prior to sending the
request. You can do this by the updateToken
method. The updateToken
method returns a promise which makes it easy to invoke the service only if the
token was successfully refreshed and display an error to the user if it wasn’t. For example:
keycloak.updateToken(30).then(function() {
loadData();
}).catch(function() {
alert('Failed to refresh token');
});
2.2.1. Session Status iframe
By default, the JavaScript adapter creates a hidden iframe that is used to detect if a Single-Sign Out has occurred.
This does not require any network traffic, instead the status is retrieved by looking at a special status cookie.
This feature can be disabled by setting checkLoginIframe: false
in the options passed to the init
method.
You should not rely on looking at this cookie directly. Its format can change and it’s also associated with the URL of the Keycloak server, not
your application.
Session Status iframe functionality is limited in some modern browsers. Please see Modern Browsers with Tracking Protection Section. |
2.2.2. Implicit and Hybrid Flow
With this flow the Keycloak server returns an authorization code, not an authentication token, to the application. The JavaScript adapter exchanges
the code
for an access token and a refresh token after the browser is redirected back to the application.
Keycloak also supports the Implicit flow where an access token
is sent immediately after successful authentication with Keycloak. This may have better performance than standard flow, as there is no additional
request to exchange the code for tokens, but it has implications when the access token expires.
However, sending the access token in the URL fragment can be a security vulnerability. For example the token could be leaked through web server logs and or
browser history.
To enable implicit flow, you need to enable the Implicit Flow Enabled
flag for the client in the Keycloak Administration Console.
You also need to pass the parameter flow
with value implicit
to init
method:
keycloak.init({
flow: 'implicit'
})
One thing to note is that only an access token is provided and there is no refresh token. This means that once the access token has expired the application
has to do the redirect to the Keycloak again to obtain a new access token.
Keycloak also supports the Hybrid flow.
This requires the client to have both the Standard Flow Enabled
and Implicit Flow Enabled
flags enabled in the admin console.
The Keycloak server will then send both the code and tokens to your application.
The access token can be used immediately while the code can be exchanged for access and refresh tokens.
Similar to the implicit flow, the hybrid flow is good for performance because the access token is available immediately.
But, the token is still sent in the URL, and the security vulnerability mentioned earlier may still apply.
One advantage in the Hybrid flow is that the refresh token is made available to the application.
For the Hybrid flow, you need to pass the parameter flow
with value hybrid
to the init
method:
keycloak.init({
flow: 'hybrid'
})
2.2.3. Hybrid Apps with Cordova
Keycloak support hybrid mobile apps developed with Apache Cordova. The JavaScript adapter has two modes for this: cordova
and cordova-native
:
The default is cordova, which the adapter will automatically select if no adapter type has been configured and window.cordova is present.
When logging in, it will open an InApp Browser that lets the user interact with Keycloak and afterwards returns to the app by redirecting to http://localhost
. Because of this, you must whitelist this URL as a valid redirect-uri in the client configuration section of the Administration Console.
While this mode is easy to setup, it also has some disadvantages:
-
The InApp-Browser is a browser embedded in the app and is not the phone’s default browser. Therefore it will have different settings and stored credentials will not be available.
-
The InApp-Browser might also be slower, especially when rendering more complex themes.
-
There are security concerns to consider, before using this mode, such as that it is possible for the app to gain access to the credentials of the user, as it has full control of the browser rendering the login page, so do not allow its use in apps you do not trust.
The alternative mode cordova-native
takes a different approach.
It opens the login page using the system’s browser.
After the user has authenticated, the browser redirects back into the app using a special URL.
From there, the Keycloak adapter can finish the login by reading the code or token from the URL.
You can activate the native mode by passing the adapter type cordova-native
to the init
method:
keycloak.init({
adapter: 'cordova-native'
})
This adapter required two additional plugins:
-
cordova-plugin-browsertab: allows the app to open webpages in the system’s browser
-
cordova-plugin-deeplinks: allow the browser to redirect back to your app by special URLs
The technical details for linking to an app differ on each platform and special setup is needed.
Please refer to the Android and iOS sections of the deeplinks plugin documentation for further instructions.
There are different kinds of links for opening apps: custom schemes (i.e. myapp://login
or android-app://com.example.myapp/https/example.com/login
) and Universal Links (iOS)) / Deep Links (Android).
While the former are easier to setup and tend to work more reliably, the later offer extra security as they are unique and only the owner of a domain can register them.
Custom-URLs are deprecated on iOS.
We recommend that you use universal links, combined with a fallback site with a custom-url link on it for best reliability.
Furthermore, we recommend the following steps to improve compatibility with the Keycloak Adapter:
-
Universal Links on iOS seem to work more reliably with
response-mode
set toquery
-
To prevent Android from opening a new instance of your app on redirect add the following snippet to
config.xml
:
<preference name="AndroidLaunchMode" value="singleTask" />
2.2.4. Custom Adapters
Sometimes it’s necessary to run the JavaScript client in environments that are not supported by default (such as Capacitor). To make it possible to use the JavasScript client in these kind of unknown environments is possible to pass a custom adapter. For example a 3rd party library could provide such an adapter to make it possible to run the JavaScript client without issues:
import Keycloak from 'keycloak-js';
import KeycloakCapacitorAdapter from 'keycloak-capacitor-adapter';
const keycloak = new Keycloak();
keycloak.init({
adapter: KeycloakCapacitorAdapter,
});
This specific package does not exist, but it gives a pretty good example of how such an adapter could be passed into the client.
It’s also possible to make your own adapter, to do so you will have to implement the methods described in the KeycloakAdapter
interface. For example the following TypeScript code ensures that all of the methods are properly implemented:
import Keycloak, { KeycloakAdapter } from 'keycloak-js';
// Implement the 'KeycloakAdapter' interface so that all required methods are guaranteed to be present.
const MyCustomAdapter: KeycloakAdapter = {
login(options) {
// Write your own implementation here.
}
// The other methods go here...
};
const keycloak = new Keycloak();
keycloak.init({
adapter: MyCustomAdapter,
});
Naturally you can also do this without TypeScript by omitting the type information, but ensuring implementing the interface properly will then be left entirely up to you.
2.2.6. Modern Browsers with Tracking Protection
In the latest versions of some browsers various cookies policies are applied to prevent tracking of the users by third-parties,
like SameSite in Chrome or completely blocked third-party cookies. It is expected that those policies will become even
more restrictive and adopted by other browsers over time, eventually leading to cookies in third-party contexts to be
completely unsupported and blocked by the browsers. The adapter features affected by this might get deprecated in the
future.
Javascript adapter relies on third-party cookies for Session Status iframe, silent check-sso
and partially also for
regular (non-silent) check-sso
. Those features have limited functionality or are completely disabled based on how
the browser is restrictive regarding cookies. The adapter tries to detect this setting and reacts accordingly.
Browsers with «SameSite=Lax by Default» Policy
All features are supported if SSL / TLS connection is configured on the Keycloak side as well as on the application
side. See configuring the SSL / TLS. Affected is e.g. Chrome starting with
version 84.
Browsers with Blocked Third-Party Cookies
Session Status iframe is not supported and is automatically disabled if such browser behavior is detected by the JS adapter.
This means the adapter cannot use session cookie for Single Sign-Out detection and have to rely purely on tokens. This
implies that when user logs out in another window, the application using JavaScript adapter won’t be logged out until it
tries to refresh the Access Token. Therefore, it is recommended to set Access Token Lifespan to relatively short time, so
that the logout is detected rather sooner than later. Please see Session and Token Timeouts.
Silent check-sso
is not supported and falls back to regular (non-silent) check-sso
by default. This behaviour can
be changed by setting silentCheckSsoFallback: false
in the options passed to the init
method. In this case, check-sso
will be completely disabled if restrictive browser behavior is detected.
Regular check-sso
is affected as well. Since Session Status iframe is unsupported, an additional redirect to Keycloak
has to be made when the adapter is initialized to check user’s login status. This is different from standard behavior when
the iframe is used to tell whether the user is logged in, and the redirect is performed only when logged out.
An affected browser is e.g. Safari starting with version 13.1.
2.2.7. JavaScript Adapter Reference
Constructor
new Keycloak();
new Keycloak('http://localhost/keycloak.json');
new Keycloak({ url: 'http://localhost/auth', realm: 'myrealm', clientId: 'myApp' });
Properties
- authenticated
-
Is
true
if the user is authenticated,false
otherwise. - token
-
The base64 encoded token that can be sent in the
Authorization
header in requests to services. - tokenParsed
-
The parsed token as a JavaScript object.
- subject
-
The user id.
- idToken
-
The base64 encoded ID token.
- idTokenParsed
-
The parsed id token as a JavaScript object.
- realmAccess
-
The realm roles associated with the token.
- resourceAccess
-
The resource roles associated with the token.
- refreshToken
-
The base64 encoded refresh token that can be used to retrieve a new token.
- refreshTokenParsed
-
The parsed refresh token as a JavaScript object.
- timeSkew
-
The estimated time difference between the browser time and the Keycloak server in seconds. This value is just an estimation, but is accurate
enough when determining if a token is expired or not. - responseMode
-
Response mode passed in init (default value is fragment).
- flow
-
Flow passed in init.
- adapter
-
Allows you to override the way that redirects and other browser-related functions will be handled by the library.
Available options:-
«default» — the library uses the browser api for redirects (this is the default)
-
«cordova» — the library will try to use the InAppBrowser cordova plugin to load keycloak login/registration pages (this is used automatically when the library is working in a cordova ecosystem)
-
«cordova-native» — the library tries to open the login and registration page using the phone’s system browser using the BrowserTabs cordova plugin. This requires extra setup for redirecting back to the app (see Hybrid Apps with Cordova).
-
custom — allows you to implement a custom adapter (only for advanced use cases)
-
- responseType
-
Response type sent to Keycloak with login requests. This is determined based on the flow value used during initialization, but can be overridden by setting this value.
Methods
init(options)
Called to initialize the adapter.
Options is an Object, where:
-
useNonce — Adds a cryptographic nonce to verify that the authentication response matches the request (default is
true
). -
onLoad — Specifies an action to do on load. Supported values are
login-required
orcheck-sso
. -
silentCheckSsoRedirectUri — Set the redirect uri for silent authentication check if onLoad is set to ‘check-sso’.
-
silentCheckSsoFallback — Enables fall back to regular
check-sso
when silentcheck-sso
is not supported by the browser (default istrue
). -
token — Set an initial value for the token.
-
refreshToken — Set an initial value for the refresh token.
-
idToken — Set an initial value for the id token (only together with token or refreshToken).
-
timeSkew — Set an initial value for skew between local time and Keycloak server in seconds (only together with token or refreshToken).
-
checkLoginIframe — Set to enable/disable monitoring login state (default is
true
). -
checkLoginIframeInterval — Set the interval to check login state (default is 5 seconds).
-
responseMode — Set the OpenID Connect response mode send to Keycloak server at login request. Valid values are
query
orfragment
. Default value isfragment
, which means that after successful authentication will Keycloak redirect to JavaScript application with OpenID Connect parameters added in URL fragment. This is generally safer and recommended overquery
. -
flow — Set the OpenID Connect flow. Valid values are
standard
,implicit
orhybrid
. -
enableLogging — Enables logging messages from Keycloak to the console (default is
false
). -
pkceMethod — The method for Proof Key Code Exchange (PKCE) to use. Configuring this value enables the PKCE mechanism. Available options:
-
«S256» — The SHA256 based PKCE method
-
Returns a promise that resolves when initialization completes.
login(options)
Redirects to login form on (options is an optional object with redirectUri and/or prompt fields).
Options is an Object, where:
-
redirectUri — Specifies the uri to redirect to after login.
-
prompt — This parameter allows to slightly customize the login flow on the Keycloak server side.
For example enforce displaying the login screen in case of valuelogin
. See Parameters Forwarding Section
for the details and all the possible values of theprompt
parameter. -
maxAge — Used just if user is already authenticated. Specifies maximum time since the authentication of user happened. If user is already authenticated for longer time than
maxAge
, the SSO is ignored and he will need to re-authenticate again. -
loginHint — Used to pre-fill the username/email field on the login form.
-
scope — Used to forward the scope parameter to the Keycloak login endpoint. Use a space-delimited list of scopes. Those typically
reference Client scopes defined on particular client. Note that the scopeopenid
will be
always be added to the list of scopes by the adapter. For example, if you enter the scope optionsaddress phone
, then the request
to Keycloak will contain the scope parameterscope=openid address phone
. -
idpHint — Used to tell Keycloak to skip showing the login page and automatically redirect to the specified identity
provider instead. More info in the Identity Provider documentation. -
action — If value is
register
then user is redirected to registration page, otherwise to login page. -
locale — Sets the ‘ui_locales’ query param in compliance with section 3.1.2.1 of the OIDC 1.0 specification.
-
kcLocale — Specifies the desired Keycloak locale for the UI. This differs from the locale param in that it tells the Keycloak server to set a cookie and update the user’s profile to a new preferred locale.
-
cordovaOptions — Specifies the arguments that are passed to the Cordova in-app-browser (if applicable). Options
hidden
andlocation
are not affected by these arguments. All available options are defined at https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-inappbrowser/. Example of use:{ zoom: "no", hardwareback: "yes" }
;
createLoginUrl(options)
Returns the URL to login form on (options is an optional object with redirectUri and/or prompt fields).
Options is an Object, which supports same options like the function login
.
logout(options)
Redirects to logout.
Options is an Object, where:
-
redirectUri — Specifies the uri to redirect to after logout.
createLogoutUrl(options)
Returns the URL to logout the user.
Options is an Object, where:
-
redirectUri — Specifies the uri to redirect to after logout.
register(options)
Redirects to registration form. Shortcut for login with option action = ‘register’
Options are same as for the login method but ‘action’ is set to ‘register’
createRegisterUrl(options)
Returns the url to registration page. Shortcut for createLoginUrl with option action = ‘register’
Options are same as for the createLoginUrl method but ‘action’ is set to ‘register’
accountManagement()
Redirects to the Account Management Console.
createAccountUrl()
Returns the URL to the Account Management Console.
hasRealmRole(role)
Returns true if the token has the given realm role.
hasResourceRole(role, resource)
Returns true if the token has the given role for the resource (resource is optional, if not specified clientId is used).
loadUserProfile()
Loads the users profile.
Returns a promise that resolves with the profile.
For example:
keycloak.loadUserProfile()
.then(function(profile) {
alert(JSON.stringify(profile, null, " "))
}).catch(function() {
alert('Failed to load user profile');
});
isTokenExpired(minValidity)
Returns true if the token has less than minValidity seconds left before it expires (minValidity is optional, if not specified 0 is used).
updateToken(minValidity)
If the token expires within minValidity seconds (minValidity is optional, if not specified 5 is used) the token is refreshed.
If the session status iframe is enabled, the session status is also checked.
Returns a promise that resolves with a boolean indicating whether or not the token has been refreshed.
For example:
keycloak.updateToken(5)
.then(function(refreshed) {
if (refreshed) {
alert('Token was successfully refreshed');
} else {
alert('Token is still valid');
}
}).catch(function() {
alert('Failed to refresh the token, or the session has expired');
});
clearToken()
Clear authentication state, including tokens.
This can be useful if application has detected the session was expired, for example if updating token fails.
Invoking this results in onAuthLogout callback listener being invoked.
Callback Events
The adapter supports setting callback listeners for certain events.
For example:
keycloak.onAuthSuccess = function() { alert('authenticated'); }
The available events are:
-
onReady(authenticated) — Called when the adapter is initialized.
-
onAuthSuccess — Called when a user is successfully authenticated.
-
onAuthError — Called if there was an error during authentication.
-
onAuthRefreshSuccess — Called when the token is refreshed.
-
onAuthRefreshError — Called if there was an error while trying to refresh the token.
-
onAuthLogout — Called if the user is logged out (will only be called if the session status iframe is enabled, or in Cordova mode).
-
onTokenExpired — Called when the access token is expired. If a refresh token is available the token can be refreshed with updateToken, or in cases where it is not (that is, with implicit flow) you can redirect to login screen to obtain a new access token.
2.3. Node.js Adapter
Keycloak provides a Node.js adapter built on top of Connect to protect server-side JavaScript apps — the goal was to be flexible enough to integrate with frameworks like Express.js.
To use the Node.js adapter, first you must create a client for your application in the Keycloak Administration Console. The adapter supports public, confidential, and bearer-only access type. Which one to choose depends on the use-case scenario.
Once the client is created click the Installation
tab, select Keycloak OIDC JSON
for Format Option
, and then click Download
. The downloaded keycloak.json
file should be at the root folder of your project.
2.3.1. Installation
Assuming you’ve already installed Node.js, create a folder for your application:
Use npm init
command to create a package.json
for your application. Now add the Keycloak connect adapter in the dependencies list:
"dependencies": {
"keycloak-connect": "11.0.3"
}
2.3.2. Usage
- Instantiate a Keycloak class
-
The
Keycloak
class provides a central point for configuration
and integration with your application. The simplest creation
involves no arguments.
var session = require('express-session');
var Keycloak = require('keycloak-connect');
var memoryStore = new session.MemoryStore();
var keycloak = new Keycloak({ store: memoryStore });
By default, this will locate a file named keycloak.json
alongside
the main executable of your application to initialize keycloak-specific
settings (public key, realm name, various URLs). The keycloak.json
file
is obtained from the Keycloak Admin Console.
Instantiation with this method results in all of the reasonable defaults
being used. As alternative, it’s also possible to provide a configuration
object, rather than the keycloak.json
file:
let kcConfig = {
clientId: 'myclient',
bearerOnly: true,
serverUrl: 'http://localhost:8080/auth',
realm: 'myrealm',
realmPublicKey: 'MIIBIjANB...'
};
let keycloak = new Keycloak({ store: memoryStore }, kcConfig);
Applications can also redirect users to their preferred identity provider by using:
let keycloak = new Keycloak({ store: memoryStore, idpHint: myIdP }, kcConfig);
- Configuring a web session store
-
If you want to use web sessions to manage
server-side state for authentication, you need to initialize the
Keycloak(…)
with at least astore
parameter, passing in the actual
session store thatexpress-session
is using.
var session = require('express-session');
var memoryStore = new session.MemoryStore();
var keycloak = new Keycloak({ store: memoryStore });
- Passing a custom scope value
-
By default, the scope value
openid
is passed as a query parameter to Keycloak’s login URL, but you can add an additional custom value:
var keycloak = new Keycloak({ scope: 'offline_access' });
2.3.3. Installing Middleware
Once instantiated, install the middleware into your connect-capable app:
var app = express();
app.use( keycloak.middleware() );
2.3.4. Configuration for Proxies
If the application is running behind a proxy that terminates an SSL connection
Express must be configured per the express behind proxies guide.
Using an incorrect proxy configuration can result in invalid redirect URIs
being generated.
Example configuration:
var app = express();
app.set( 'trust proxy', true );
app.use( keycloak.middleware() );
2.3.5. Checking Authentication
To check that a user is authenticated before accessing a resource,
simply use keycloak.checkSso()
. It will only authenticate if the user is already logged-in. If the user is not logged-in, the browser will be redirected back to the originally-requested URL and remain unauthenticated:
app.get( '/check-sso', keycloak.checkSso(), checkSsoHandler );
2.3.6. Protecting Resources
- Simple authentication
-
To enforce that a user must be authenticated before accessing a resource,
simply use a no-argument version ofkeycloak.protect()
:
app.get( '/complain', keycloak.protect(), complaintHandler );
- Role-based authorization
-
To secure a resource with an application role for the current app:
app.get( '/special', keycloak.protect('special'), specialHandler );
To secure a resource with an application role for a different app:
app.get( '/extra-special', keycloak.protect('other-app:special'), extraSpecialHandler );
To secure a resource with a realm role:
app.get( '/admin', keycloak.protect( 'realm:admin' ), adminHandler );
- Resource-Based Authorization
-
Resource-Based Authorization allows you to protect resources, and their specific methods/actions,** based on a set of policies defined in Keycloak, thus externalizing authorization from your application. This is achieved by exposing a
keycloak.enforcer
method which you can use to protect resources.*
app.get('/apis/me', keycloak.enforcer('user:profile'), userProfileHandler);
The keycloak-enforcer
method operates in two modes, depending on the value of the response_mode
configuration option.
app.get('/apis/me', keycloak.enforcer('user:profile', {response_mode: 'token'}), userProfileHandler);
If response_mode
is set to token
, permissions are obtained from the server on behalf of the subject represented by the bearer token that was sent to your application. In this case, a new access token is issued by Keycloak with the permissions granted by the server. If the server did not respond with a token with the expected permissions, the request is denied. When using this mode, you should be able to obtain the token from the request as follows:
app.get('/apis/me', keycloak.enforcer('user:profile', {response_mode: 'token'}), function (req, res) {
var token = var token = req.kauth.grant.access_token.content;
var permissions = token.authorization ? token.authorization.permissions : undefined;
// show user profile
});
Prefer this mode when your application is using sessions and you want to cache previous decisions from the server, as well automatically handle refresh tokens. This mode is especially useful for applications acting as a client and resource server.
If response_mode
is set to permissions
(default mode), the server only returns the list of granted permissions, without issuing a new access token. In addition to not issuing a new token, this method exposes the permissions granted by the server through the request
as follows:
app.get('/apis/me', keycloak.enforcer('user:profile', {response_mode: 'token'}), function (req, res) {
var permissions = req.permissions;
// show user profile
});
Regardless of the response_mode
in use, the keycloak.enforcer
method will first try to check the permissions within the bearer token that was sent to your application. If the bearer token already carries the expected permissions, there is no need
to interact with the server to obtain a decision. This is specially useful when your clients are capable of obtaining access tokens from the server with the expected permissions before accessing a protected resource, so they can use some capabilities provided by Keycloak Authorization Services such as incremental authorization and avoid additional requests to the server when keycloak.enforcer
is enforcing access to the resource.
By default, the policy enforcer will use the client_id
defined to the application (for instance, via keycloak.json
) to
reference a client in Keycloak that supports Keycloak Authorization Services. In this case, the client can not be public given
that it is actually a resource server.
If your application is acting as both a public client(frontend) and resource server(backend), you can use the following configuration to reference a different
client in Keycloak with the policies that you want to enforce:
keycloak.enforcer('user:profile', {resource_server_id: 'my-apiserver'})
It is recommended to use distinct clients in Keycloak to represent your frontend and backend.
If the application you are protecting is enabled with Keycloak authorization services and you have defined client credentials
in keycloak.json
, you can push additional claims to the server and make them available to your policies in order to make decisions.
For that, you can define a claims
configuration option which expects a function
that returns a JSON with the claims you want to push:
app.get('/protected/resource', keycloak.enforcer(['resource:view', 'resource:write'], {
claims: function(request) {
return {
"http.uri": ["/protected/resource"],
"user.agent": // get user agent from request
}
}
}), function (req, res) {
// access granted
For more details about how to configure Keycloak to protected your application resources, please take a look at the Authorization Services Guide.
- Advanced authorization
-
To secure resources based on parts of the URL itself, assuming a role exists
for each section:
function protectBySection(token, request) {
return token.hasRole( request.params.section );
}
app.get( '/:section/:page', keycloak.protect( protectBySection ), sectionHandler );
2.3.7. Additional URLs
- Explicit user-triggered logout
-
By default, the middleware catches calls to
/logout
to send the user through a
Keycloak-centric logout workflow. This can be changed by specifying alogout
configuration parameter to themiddleware()
call:
app.use( keycloak.middleware( { logout: '/logoff' } ));
When the user-triggered logout is invoked a query parameter redirect_url
can be passed:
https://example.com/logoff?redirect_url=https%3A%2F%2Fexample.com%3A3000%2Flogged%2Fout
This parameter is then used as the redirect url of the OIDC logout endpoint and the user will be redirected to
https://example.com/logged/out
.
- Keycloak Admin Callbacks
-
Also, the middleware supports callbacks from the Keycloak console to log out a single
session or all sessions. By default, these type of admin callbacks occur relative
to the root URL of/
but can be changed by providing anadmin
parameter
to themiddleware()
call:
app.use( keycloak.middleware( { admin: '/callbacks' } );
2.4. Keycloak Gatekeeper
Keycloak provides a Go programming language adapter for use with OpenID Connect (OIDC) that supports both access tokens in a browser cookie or bearer tokens.
This documentation details how to build and configure keycloak-gatekeeper followed by details of how to use each of its features.
For further information, see the included help file which includes a full list of commands and switches. View the file by entering the following at the command line (modify the location to match where you install keycloak-gatekeeper):
$ bin/keycloak-gatekeeper help
2.4.1. Building
Prerequisites
-
Golang must be installed.
-
Make must be installed.
Procedure
-
Run
make dep-install
to install all needed dependencies. -
Run
make test
to run the included tests. -
Run
make
to build the project. You can instead usemake static
if you prefer to build a binary that includes within it all of the required dependencies.
2.4.2. Configuration options
Configuration can come from a yaml/json file or by using command line options. Here is a list of options.
# is the url for retrieve the OpenID configuration - normally the <server>/auth/realm/<realm_name>
discovery-url: https://keycloak.example.com/auth/realms/<REALM_NAME>
# the client id for the 'client' application
client-id: <CLIENT_ID>
# the secret associated to the 'client' application
client-secret: <CLIENT_SECRET>
# the interface definition you wish the proxy to listen, all interfaces is specified as ':<port>', unix sockets as unix://<REL_PATH>|</ABS PATH>
listen: :3000
# whether to enable refresh tokens
enable-refresh-tokens: true
# the location of a certificate you wish the proxy to use for TLS support
tls-cert:
# the location of a private key for TLS
tls-private-key:
# the redirection url, essentially the site url, note: /oauth/callback is added at the end
redirection-url: http://127.0.0.1:3000
# the encryption key used to encode the session state
encryption-key: <ENCRYPTION_KEY>
# the upstream endpoint which we should proxy request
upstream-url: http://127.0.0.1:80
# additional scopes to add to the default (openid+email+profile)
scopes:
- vpn-user
# a collection of resource i.e. urls that you wish to protect
resources:
- uri: /admin/test
# the methods on this url that should be protected, if missing, we assuming all
methods:
- GET
# a list of roles the user must have in order to access urls under the above
# If all you want is authentication ONLY, simply remove the roles array - the user must be authenticated but
# no roles are required
roles:
- openvpn:vpn-user
- openvpn:prod-vpn
- test
- uri: /admin/*
methods:
- GET
roles:
- openvpn:vpn-user
- openvpn:commons-prod-vpn
Options issued at the command line have a higher priority and will override or merge with options referenced in a config file. Examples of each style are shown here.
2.4.3. Example usage and configuration
Assuming you have some web service you wish protected by Keycloak:
-
Create the client using the Keycloak GUI or CLI; the client protocol is ‘openid-connect’, access-type: confidential.
-
Add a Valid Redirect URI of http://127.0.0.1:3000/oauth/callback.
-
Grab the client id and client secret.
-
Create the various roles under the client or existing clients for authorization purposes.
Here is an example configuration file.
client-id: <CLIENT_ID>
client-secret: <CLIENT_SECRET> # require for access_type: confidential
# Note the redirection-url is optional, it will default to the X-Forwarded-Proto / X-Forwarded-Host r the URL scheme and host not found
discovery-url: https://keycloak.example.com/auth/realms/<REALM_NAME>
enable-default-deny: true
encryption_key: AgXa7xRcoClDEU0ZDSH4X0XhL5Qy2Z2j
listen: :3000
redirection-url: http://127.0.0.1:3000
upstream-url: http://127.0.0.1:80
resources:
- uri: /admin*
methods:
- GET
roles:
- client:test1
- client:test2
require-any-role: true
groups:
- admins
- users
- uri: /backend*
roles:
- client:test1
- uri: /public/*
white-listed: true
- uri: /favicon
white-listed: true
- uri: /css/*
white-listed: true
- uri: /img/*
white-listed: true
headers:
myheader1: value_1
myheader2: value_2
Anything defined in a configuration file can also be configured using command line options, such as in this example.
bin/keycloak-gatekeeper
--discovery-url=https://keycloak.example.com/auth/realms/<REALM_NAME>
--client-id=<CLIENT_ID>
--client-secret=<SECRET>
--listen=127.0.0.1:3000 # unix sockets format unix://path
--redirection-url=http://127.0.0.1:3000
--enable-refresh-tokens=true
--encryption-key=AgXa7xRcoClDEU0ZDSH4X0XhL5Qy2Z2j
--upstream-url=http://127.0.0.1:80
--enable-default-deny=true
--resources="uri=/admin*|roles=test1,test2"
--resources="uri=/backend*|roles=test1"
--resources="uri=/css/*|white-listed=true"
--resources="uri=/img/*|white-listed=true"
--resources="uri=/public/*|white-listed=true"
--headers="myheader1=value1"
--headers="myheader2=value2"
By default the roles defined on a resource perform a logical AND
so all roles specified must be present in the claims, this behavior can be altered by the require-any-role
option, however, so as long as one role is present the permission is granted.
2.4.4. OpenID Provider Communication
By default the communication with the OpenID provider is direct. If you wish, you can specify a forwarding proxy server in your configuration file:
openid-provider-proxy: http://proxy.example.com:8080
2.4.5. HTTP routing
By default all requests will be proxyed on to the upstream, if you wish to ensure all requests are authenticated you can use this:
--resource=uri=/* # note, unless specified the method is assumed to be 'any|ANY'
The HTTP routing rules follow the guidelines from chi. The ordering of the resources do not matter, the router will handle that for you.
2.4.6. Session-only cookies
By default the access and refresh cookies are session-only and disposed of on browser close; you can disable this feature using the --enable-session-cookies
option.
2.4.7. Forward-signing proxy
Forward-signing provides a mechanism for authentication and authorization between services using tokens issued from the IdP. When operating in this mode the proxy will automatically acquire an access token (handling the refreshing or logins on your behalf) and tag outbound requests with a Authorization header. You can control which domains are tagged with the —forwarding-domains option. Note, this option use a contains comparison on domains. So, if you wanted to match all domains under *.svc.cluster.local you can use: —forwarding-domain=svc.cluster.local.
At present the service performs a login using oauth client_credentials grant type, so your IdP service must support direct (username/password) logins.
Example setup:
You have collection of micro-services which are permitted to speak to one another; you have already set up the credentials, roles, and clients in Keycloak, providing granular role controls over issue tokens.
- name: keycloak-gatekeeper
image: quay.io/gambol99/keycloak-generic-adapter:latest
args:
- --enable-forwarding=true
- --forwarding-username=projecta
- --forwarding-password=some_password
- --forwarding-domains=projecta.svc.cluster.local
- --forwarding-domains=projectb.svc.cluster.local
- --tls-ca-certificate=/etc/secrets/ca.pem
- --tls-ca-key=/etc/secrets/ca-key.pem
# Note: if you don't specify any forwarding domains, all domains will be signed; Also the code checks is the
# domain 'contains' the value (it's not a regex) so if you wanted to sign all requests to svc.cluster.local, just use
# svc.cluster.local
volumeMounts:
- name: keycloak-socket
mountPoint: /var/run/keycloak
- name: projecta
image: some_images
# test the forward proxy
$ curl -k --proxy http://127.0.0.1:3000 https://test.projesta.svc.cluster.local
On the receiver side you could set up the Keycloak Gatekeeper (—no=redirects=true) and permit this to verify and handle admission for you. Alternatively, the access token can found as a bearer token in the request.
2.4.8. Forwarding signed HTTPS connections
Handling HTTPS requires a man-in-the-middle sort of TLS connection. By default, if no --tls-ca-certificate
and --tls-ca-key
are provided the proxy will use the default certificate. If you wish to verify the trust, you’ll need to generate a CA, for example.
$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ca.key -out ca.pem
$ bin/keycloak-gatekeeper
--enable-forwarding
--forwarding-username=USERNAME
--forwarding-password=PASSWORD
--client-id=CLIENT_ID
--client-secret=SECRET
--discovery-url=https://keycloak.example.com/auth/realms/test
--tls-ca-certificate=ca.pem
--tls-ca-key=ca-key.pem
2.4.9. HTTPS redirect
The proxy supports an HTTP listener, so the only real requirement here is to perform an HTTP → HTTPS redirect. You can enable the option like this:
--listen-http=127.0.0.1:80
--enable-security-filter=true # is required for the https redirect
--enable-https-redirection
2.4.10. Let’s Encrypt configuration
Here is an example of the required configuration for Let’s Encrypt support:
listen: 0.0.0.0:443
enable-https-redirection: true
enable-security-filter: true
use-letsencrypt: true
letsencrypt-cache-dir: ./cache/
redirection-url: https://domain.tld:443/
hostnames:
- domain.tld
Listening on port 443 is mandatory.
2.4.11. Access token encryption
By default, the session token is placed into a cookie in plaintext. If you prefer to encrypt the session cookie, use the --enable-encrypted-token
and --encryption-key
options. Note that the access token forwarded in the X-Auth-Token header to upstream is unaffected.
On protected resources, the upstream endpoint will receive a number of headers added by the proxy, along with custom claims, like this:
# add the header to the upstream endpoint
id := user.(*userContext)
cx.Request().Header.Set("X-Auth-Email", id.email)
cx.Request().Header.Set("X-Auth-ExpiresIn", id.expiresAt.String())
cx.Request().Header.Set("X-Auth-Groups", strings.Join(id.groups, ","))
cx.Request().Header.Set("X-Auth-Roles", strings.Join(id.roles, ","))
cx.Request().Header.Set("X-Auth-Subject", id.id)
cx.Request().Header.Set("X-Auth-Token", id.token.Encode())
cx.Request().Header.Set("X-Auth-Userid", id.name)
cx.Request().Header.Set("X-Auth-Username", id.name)
// step: add the authorization header if requested
if r.config.EnableAuthorizationHeader {
cx.Request().Header.Set("Authorization", fmt.Sprintf("Bearer %s", id.token.Encode()))
}
To control the Authorization
header use the enable-authorization-header
yaml configuration or the --enable-authorization-header
command line option. By default this option is set to true
.
You can inject additional claims from the access token into the authorization headers with the --add-claims
option. For example, a token from a Keycloak provider might include the following claims:
"resource_access": {},
"name": "Beloved User",
"preferred_username": "beloved.user",
"given_name": "Beloved",
"family_name": "User",
"email": "beloved@example.com"
In order to request you receive the given_name, family_name and name in the authentication header we would add --add-claims=given_name
and --add-claims=family_name
and so on, or we can do it in the configuration file, like this:
add-claims:
- given_name
- family_name
- name
This would add the additional headers to the authenticated request along with standard ones.
X-Auth-Family-Name: User
X-Auth-Given-Name: Beloved
X-Auth-Name: Beloved User
You can inject custom headers using the --headers="name=value"
option or the configuration file:
2.4.15. Encryption key
In order to remain stateless and not have to rely on a central cache to persist the refresh_tokens, the refresh token is encrypted and added as a cookie using crypto/aes. The key must be the same if you are running behind a load balancer. The key length should be either 16 or 32 bytes, depending or whether you want AES-128 or AES-256.
2.4.16. Claim matching
The proxy supports adding a variable list of claim matches against the presented tokens for additional access control. You can match the ‘iss’ or ‘aud’ to the token or custom attributes; each of the matches are regex’s. For example, --match-claims 'aud=sso.*'
or --claim iss=https://.*'
or via the configuration file, like this:
match-claims:
aud: openvpn
iss: https://keycloak.example.com/auth/realms/commons
or via the CLI, like this:
--match-claims=auth=openvpn
--match-claims=iss=http://keycloak.example.com/realms/commons
You can limit the email domain permitted; for example if you want to limit to only users on the example.com domain:
match-claims:
email: ^.*@example.com$
The adapter supports matching on multi-value strings claims. The match will succeed if one of the values matches, for example:
match-claims:
perms: perm1
will successfully match
{
"iss": "https://sso.example.com",
"sub": "",
"perms": ["perm1", "perm2"]
}
2.4.17. Group claims
You can match on the group claims within a token via the groups
parameter available within the resource. While roles are implicitly required, such as roles=admin,user
where the user MUST have roles ‘admin’ AND ‘user’, groups are applied with an OR operation, so groups=users,testers
requires that the user MUST be within either ‘users’ OR ‘testers’. The claim name is hard-coded to groups
, so a JWT token would look like this:
{
"iss": "https://sso.example.com",
"sub": "",
"aud": "test",
"exp": 1515269245,
"iat": 1515182845,
"email": "beloved@example.com",
"groups": [
"group_one",
"group_two"
],
"name": "Beloved"
}
2.4.18. Custom pages
By default, Keycloak Gatekeeper will immediately redirect you for authentication and hand back a 403 for access denied. Most users will probably want to present the user with a more friendly sign-in and access denied page. You can pass the command line options (or via config file) paths to the files with --signin-page=PATH
. The sign-in page will have a ‘redirect’ variable passed into the scope and holding the oauth redirection url. If you wish to pass additional variables into the templates, such as title, sitename and so on, you can use the —-tags key=pair
option, like this: --tags title="This is my site"
and the variable would be accessible from {{ .title }}
.
<html>
<body>
<a href="{{ .redirect }}">Sign-in</a>
</body>
</html>
2.4.19. White-listed URL’s
Depending on how the application URL’s are laid out, you might want protect the root / url but have exceptions on a list of paths, for example /health
. While this is best solved by adjusting the paths, you can add exceptions to the protected resources, like this:
resources:
- uri: /some_white_listed_url
white-listed: true
- uri: /*
methods:
- GET
roles:
- <CLIENT_APP_NAME>:<ROLE_NAME>
- <CLIENT_APP_NAME>:<ROLE_NAME>
Or on the command line
--resources "uri=/some_white_listed_url|white-listed=true"
--resources "uri=/*" # requires authentication on the rest
--resources "uri=/admin*|roles=admin,superuser|methods=POST,DELETE"
2.4.20. Mutual TLS
The proxy support enforcing mutual TLS for the clients by adding the --tls-ca-certificate
command line option or configuration file option. All clients connecting must present a certificate which was signed by the CA being used.
2.4.21. Certificate rotation
The proxy will automatically rotate the server certificates if the files change on disk. Note, no down time will occur as the change is made inline. Clients who connected prior to the certificate rotation will be unaffected and will continue as normal with all new connections presented with the new certificate.
2.4.22. Refresh tokens
If a request for an access token contains a refresh token and --enable-refresh-tokens
is set to true
, the proxy will automatically refresh the access token for you. The tokens themselves are kept either as an encrypted (—encryption-key=KEY) cookie (cookie name: kc-state). or a store (still requires encryption key).
At present the only store options supported are Redis and Boltdb.
To enable a local boltdb store use --store-url boltdb:///PATH
or using a relative path boltdb://PATH
.
To enable a local redis store use redis://[USER:PASSWORD@]HOST:PORT
. In both cases the refresh token is encrypted before being placed into the store.
2.4.23. Logout endpoint
A /oauth/logout?redirect=url is provided as a helper to log users out. In addition to dropping any session cookies, we also attempt to revoke access via revocation url (config revocation-url or —revocation-url) with the provider. For Keycloak, the url for this would be https://keycloak.example.com/auth/realms/REALM_NAME/protocol/openid-connect/logout. If the url is not specified we will attempt to grab the url from the OpenID discovery response.
2.4.24. Cross-origin resource sharing (CORS)
You can add a CORS header via the --cors-[method]
with these configuration options.
-
Access-Control-Allow-Origin
-
Access-Control-Allow-Methods
-
Access-Control-Allow-Headers
-
Access-Control-Expose-Headers
-
Access-Control-Allow-Credentials
-
Access-Control-Max-Age
You can add using the config file:
cors-origins:
- '*'
cors-methods:
- GET
- POST
or via the command line:
--cors-origins [--cors-origins option] a set of origins to add to the CORS access control (Access-Control-Allow-Origin)
--cors-methods [--cors-methods option] the method permitted in the access control (Access-Control-Allow-Methods)
--cors-headers [--cors-headers option] a set of headers to add to the CORS access control (Access-Control-Allow-Headers)
--cors-exposes-headers [--cors-exposes-headers option] set the expose cors headers access control (Access-Control-Expose-Headers)
2.4.25. Upstream URL
You can control the upstream endpoint via the --upstream-url
option. Both HTTP and HTTPS are supported with TLS verification and keep-alive support configured via the --skip-upstream-tls-verify
/ --upstream-keepalives
option. Note, the proxy can also upstream via a UNIX socket, --upstream-url unix://path/to/the/file.sock
.
2.4.26. Endpoints
-
/oauth/authorize is authentication endpoint which will generate the OpenID redirect to the provider
-
/oauth/callback is provider OpenID callback endpoint
-
/oauth/expired is a helper endpoint to check if a access token has expired, 200 for ok and, 401 for no token and 401 for expired
-
/oauth/health is the health checking endpoint for the proxy, you can also grab version from headers
-
/oauth/login provides a relay endpoint to login via
grant_type=password
, for example,POST /oauth/login
form values areusername=USERNAME&password=PASSWORD
(must be enabled) -
/oauth/logout provides a convenient endpoint to log the user out, it will always attempt to perform a back channel log out of offline tokens
-
/oauth/token is a helper endpoint which will display the current access token for you
-
/oauth/metrics is a Prometheus metrics handler
2.4.27. Metrics
Assuming --enable-metrics
has been set, a Prometheus endpoint can be found on /oauth/metrics; at present the only metric being exposed is a counter per HTTP code.
2.4.28. Limitations
Keep in mind browser cookie limits if you use access or refresh tokens in the browser cookie. Keycloak-generic-adapter divides the cookie automatically if your cookie is longer than 4093 bytes. Real size of the cookie depends on the content of the issued access token. Also, encryption might add additional bytes to the cookie size. If you have large cookies (>200 KB), you might reach browser cookie limits.
All cookies are part of the header request, so you might find a problem with the max headers size limits in your infrastructure (some load balancers have very low this value, such as 8 KB). Be sure that all network devices have sufficient header size limits. Otherwise, your users won’t be able to obtain an access token.
2.4.29. Known Issues
There is a known issue with the Keycloak server 4.6.0.Final in which Gatekeeper is unable to find the client_id in the aud claim. This is due to the fact the client_id is not in the audience anymore. The workaround is to add the «Audience» protocol mapper to the client with the audience pointed to the client_id. For more information, see KEYCLOAK-8954.
2.5. mod_auth_openidc Apache HTTPD Module
The mod_auth_openidc is an Apache HTTP plugin for OpenID Connect. If your language/environment supports using Apache HTTPD
as a proxy, then you can use mod_auth_openidc to secure your web application with OpenID Connect. Configuration of this module
is beyond the scope of this document. Please see the mod_auth_openidc GitHub repo for more details on configuration.
To configure mod_auth_openidc you’ll need
-
The client_id.
-
The client_secret.
-
The redirect_uri to your application.
-
The Keycloak openid-configuration url
-
mod_auth_openidc specific Apache HTTPD module config.
An example configuration would look like the following.
LoadModule auth_openidc_module modules/mod_auth_openidc.so
ServerName ${HOSTIP}
<VirtualHost *:80>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
#this is required by mod_auth_openidc
OIDCCryptoPassphrase a-random-secret-used-by-apache-oidc-and-balancer
OIDCProviderMetadataURL ${KC_ADDR}/auth/realms/${KC_REALM}/.well-known/openid-configuration
OIDCClientID ${CLIENT_ID}
OIDCClientSecret ${CLIENT_SECRET}
OIDCRedirectURI http://${HOSTIP}/${CLIENT_APP_NAME}/redirect_uri
# maps the prefered_username claim to the REMOTE_USER environment variable
OIDCRemoteUserClaim preferred_username
<Location /${CLIENT_APP_NAME}/>
AuthType openid-connect
Require valid-user
</Location>
</VirtualHost>
Further information on how to configure mod_auth_openidc can be found on the mod_auth_openidc
project page.
2.6. Other OpenID Connect Libraries
Keycloak can be secured by supplied adapters that are usually easier to use and provide better integration with Keycloak. However, if an adapter is not available for your programming language, framework, or platform you might opt to use a generic OpenID Connect Relying Party (RP) library instead. This chapter describes details specific to Keycloak and does not contain specific protocol details. For more information see the OpenID Connect specifications and OAuth2 specification.
2.6.1. Endpoints
The most important endpoint to understand is the well-known
configuration endpoint. It lists endpoints and other configuration options relevant to the OpenID Connect implementation in Keycloak. The endpoint is:
/realms/{realm-name}/.well-known/openid-configuration
To obtain the full URL, add the base URL for Keycloak and replace {realm-name}
with the name of your realm. For example:
http://localhost:8080/auth/realms/master/.well-known/openid-configuration
Some RP libraries retrieve all required endpoints from this endpoint, but for others you might need to list the endpoints individually.
/realms/{realm-name}/protocol/openid-connect/auth
The authorization endpoint performs authentication of the end-user. This is done by redirecting the user agent to this endpoint.
For more details see the Authorization Endpoint section in the OpenID Connect specification.
Token Endpoint
/realms/{realm-name}/protocol/openid-connect/token
The token endpoint is used to obtain tokens. Tokens can either be obtained by exchanging an authorization code or by supplying credentials directly depending on what flow is used.
The token endpoint is also used to obtain new access tokens when they expire.
For more details see the Token Endpoint section in the OpenID Connect specification.
Userinfo Endpoint
/realms/{realm-name}/protocol/openid-connect/userinfo
The userinfo endpoint returns standard claims about the authenticated user, and is protected by a bearer token.
For more details see the Userinfo Endpoint section in the OpenID Connect specification.
Logout Endpoint
/realms/{realm-name}/protocol/openid-connect/logout
The logout endpoint logs out the authenticated user.
The user agent can be redirected to the endpoint, in which case the active user session is logged out. Afterward the user agent is redirected back to the application.
The endpoint can also be invoked directly by the application. To invoke this endpoint directly the refresh token needs to be included as well as the credentials required to authenticate the client.
Certificate Endpoint
/realms/{realm-name}/protocol/openid-connect/certs
The certificate endpoint returns the public keys enabled by the realm, encoded as a JSON Web Key (JWK). Depending on the realm settings there can be one or more keys enabled for verifying tokens. For more information see the Server Administration Guide and the JSON Web Key specification.
Introspection Endpoint
/realms/{realm-name}/protocol/openid-connect/token/introspect
The introspection endpoint is used to retrieve the active state of a token. In other words, you can use it to validate an access or refresh token.
It can only be invoked by confidential clients.
Dynamic Client Registration Endpoint
/realms/{realm-name}/clients-registrations/openid-connect
The dynamic client registration endpoint is used to dynamically register clients.
Token Revocation Endpoint
/realms/{realm-name}/protocol/openid-connect/revoke
The token revocation endpoint is used to revoke tokens.
2.6.2. Validating Access Tokens
If you need to manually validate access tokens issued by Keycloak you can invoke the Introspection Endpoint.
The downside to this approach is that you have to make a network invocation to the Keycloak server. This can be slow and possibily overload the
server if you have too many validation requests going on at the same time. Keycloak issued access tokens are JSON Web Tokens (JWT) digitally signed and encoded using JSON Web Signature (JWS).
Because they are encoded in this way, this allows you to locally validate access tokens using the public key of the issuing realm. You can either hard code the
realm’s public key in your validation code, or lookup and cache the public key using the certificate endpoint with the Key ID (KID) embedded within the
JWS. Depending what language you code in, there are a multitude of third party libraries out there that can help you with JWS validation.
2.6.3. Flows
Authorization Code
The Authorization Code flow redirects the user agent to Keycloak. Once the user has successfully authenticated with Keycloak an
Authorization Code is created and the user agent is redirected back to the application. The application then uses the authorization code along with its
credentials to obtain an Access Token, Refresh Token and ID Token from Keycloak.
The flow is targeted towards web applications, but is also recommended for native applications, including mobile applications, where it is possible to embed
a user agent.
Implicit
The Implicit flow redirects works similarly to the Authorization Code flow, but instead of returning an Authorization Code the Access Token and ID Token is
returned. This reduces the need for the extra invocation to exchange the Authorization Code for an Access Token. However, it does not include a Refresh
Token. This results in the need to either permit Access Tokens with a long expiration, which is problematic as it’s very hard to invalidate these. Or
requires a new redirect to obtain new Access Token once the initial Access Token has expired. The Implicit flow is useful if the application only wants to
authenticate the user and deals with logout itself.
There’s also a Hybrid flow where both the Access Token and an Authorization Code is returned.
One thing to note is that both the Implicit flow and Hybrid flow has potential security risks as the Access Token may be leaked through web server logs and
browser history. This is somewhat mitigated by using short expiration for Access Tokens.
For more details refer to the Implicit Flow in the OpenID Connect specification.
Resource Owner Password Credentials
Resource Owner Password Credentials, referred to as Direct Grant in Keycloak, allows exchanging user credentials for tokens. It’s not recommended
to use this flow unless you absolutely need to. Examples where this could be useful are legacy applications and command-line interfaces.
There are a number of limitations of using this flow, including:
-
User credentials are exposed to the application
-
Applications need login pages
-
Application needs to be aware of the authentication scheme
-
Changes to authentication flow requires changes to application
-
No support for identity brokering or social login
-
Flows are not supported (user self-registration, required actions, etc.)
For a client to be permitted to use the Resource Owner Password Credentials grant the client has to have the Direct Access Grants Enabled
option enabled.
This flow is not included in OpenID Connect, but is a part of the OAuth 2.0 specification.
Example using CURL
The following example shows how to obtain an access token for a user in the realm master
with username user
and password password
. The example is using
the confidential client myclient
:
curl
-d "client_id=myclient"
-d "client_secret=40cc097b-2a57-4c17-b36a-8fdf3fc2d578"
-d "username=user"
-d "password=password"
-d "grant_type=password"
"http://localhost:8080/auth/realms/master/protocol/openid-connect/token"
Client Credentials
Client Credentials is used when clients (applications and services) wants to obtain access on behalf of themselves rather than on behalf of a user. This can
for example be useful for background services that applies changes to the system in general rather than for a specific user.
Keycloak provides support for clients to authenticate either with a secret or with public/private keys.
This flow is not included in OpenID Connect, but is a part of the OAuth 2.0 specification.
2.6.4. Redirect URIs
When using the redirect based flows it’s important to use valid redirect uris for your clients. The redirect uris should be as specific as possible. This
especially applies to client-side (public clients) applications. Failing to do so could result in:
-
Open redirects — this can allow attackers to create spoof links that looks like they are coming from your domain
-
Unauthorized entry — when users are already authenticated with Keycloak an attacker can use a public client where redirect uris have not be configured correctly to gain access by redirecting the user without the users knowledge
In production for web applications always use https
for all redirect URIs. Do not allow redirects to http.
There’s also a few special redirect URIs:
http://localhost
-
This redirect URI is useful for native applications and allows the native application to create a web server on a random port that can be used to obtain the
authorization code. This redirect uri allows any port.
urn:ietf:wg:oauth:2.0:oob
-
If its not possible to start a web server in the client (or a browser is not available) it is possible to use the special
urn:ietf:wg:oauth:2.0:oob
redirect uri.
When this redirect uri is used Keycloak displays a page with the code in the title and in a box on the page.
The application can either detect that the browser title has changed, or the user can copy/paste the code manually to the application.
With this redirect uri it is also possible for a user to use a different device to obtain a code to paste back to the application.
3. SAML
This section describes how you can secure applications and services with SAML using either Keycloak client adapters or generic
SAML provider libraries.
3.1. Java Adapters
Keycloak comes with a range of different adapters for Java application. Selecting the correct adapter depends on the target platform.
3.1.1. General Adapter Config
Each SAML client adapter supported by Keycloak can be configured by a simple XML text file.
This is what one might look like:
<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:keycloak:saml:adapter https://www.keycloak.org/schema/keycloak_saml_adapter_1_10.xsd">
<SP entityID="http://localhost:8081/sales-post-sig/"
sslPolicy="EXTERNAL"
nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
logoutPage="/logout.jsp"
forceAuthentication="false"
isPassive="false"
turnOffChangeSessionIdOnLogin="false"
autodetectBearerOnly="false">
<Keys>
<Key signing="true" >
<KeyStore resource="/WEB-INF/keystore.jks" password="store123">
<PrivateKey alias="http://localhost:8080/sales-post-sig/" password="test123"/>
<Certificate alias="http://localhost:8080/sales-post-sig/"/>
</KeyStore>
</Key>
</Keys>
<PrincipalNameMapping policy="FROM_NAME_ID"/>
<RoleIdentifiers>
<Attribute name="Role"/>
</RoleIdentifiers>
<RoleMappingsProvider id="properties-based-role-mapper">
<Property name="properties.resource.location" value="/WEB-INF/role-mappings.properties"/>
</RoleMappingsProvider>
<IDP entityID="idp"
signaturesRequired="true">
<SingleSignOnService requestBinding="POST"
bindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
/>
<SingleLogoutService
requestBinding="POST"
responseBinding="POST"
postBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
redirectBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
/>
<Keys>
<Key signing="true">
<KeyStore resource="/WEB-INF/keystore.jks" password="store123">
<Certificate alias="demo"/>
</KeyStore>
</Key>
</Keys>
</IDP>
</SP>
</keycloak-saml-adapter>
Some of these configuration switches may be adapter specific and some are common across all adapters.
For Java adapters you can use ${…}
enclosure as System property replacement.
For example ${jboss.server.config.dir}
.
SP Element
Here is the explanation of the SP element attributes:
<SP entityID="sp"
sslPolicy="ssl"
nameIDPolicyFormat="format"
forceAuthentication="true"
isPassive="false"
keepDOMAssertion="true"
autodetectBearerOnly="false">
...
</SP>
- entityID
-
This is the identifier for this client.
The IdP needs this value to determine who the client is that is communicating with it. This setting is REQUIRED. - sslPolicy
-
This is the SSL policy the adapter will enforce.
Valid values are:ALL
,EXTERNAL
, andNONE
.
ForALL
, all requests must come in via HTTPS.
ForEXTERNAL
, only non-private IP addresses must come over the wire via HTTPS.
ForNONE
, no requests are required to come over via HTTPS.
This setting is OPTIONAL. Default value isEXTERNAL
. - nameIDPolicyFormat
-
SAML clients can request a specific NameID Subject format.
Fill in this value if you want a specific format.
It must be a standard SAML format identifier:urn:oasis:names:tc:SAML:2.0:nameid-format:transient
.
This setting is OPTIONAL.
By default, no special format is requested. - forceAuthentication
-
SAML clients can request that a user is re-authenticated even if they are already logged in at the IdP.
Set this totrue
to enable. This setting is OPTIONAL.
Default value isfalse
. - isPassive
-
SAML clients can request that a user is never asked to authenticate even if they are not logged in at the IdP.
Set this totrue
if you want this.
Do not use together withforceAuthentication
as they are opposite. This setting is OPTIONAL.
Default value isfalse
. - turnOffChangeSessionIdOnLogin
-
The session ID is changed by default on a successful login on some platforms to plug a security attack vector.
Change this totrue
to disable this. It is recommended you do not turn it off.
Default value isfalse
. - autodetectBearerOnly
-
This should be set to true if your application serves both a web application and web services (e.g. SOAP or REST).
It allows you to redirect unauthenticated users of the web application to the Keycloak login page,
but send an HTTP401
status code to unauthenticated SOAP or REST clients instead as they would not understand a redirect to the login page.
Keycloak auto-detects SOAP or REST clients based on typical headers likeX-Requested-With
,SOAPAction
orAccept
.
The default value is false. - logoutPage
-
This sets the page to display after logout. If the page is a full URL, such as
http://web.example.com/logout.html
,
the user is redirected after logout to that page using the HTTP302
status code. If a link without scheme part is specified,
such as/logout.jsp
, the page is displayed after logout, regardless of whether it lies in a protected area according
tosecurity-constraint
declarations in web.xml, and the page is resolved relative to the deployment context root. - keepDOMAssertion
-
This attribute should be set to true to make the adapter store the DOM representation of the assertion in its
original form inside theSamlPrincipal
associated to the request. The assertion document can be retrieved using
the methodgetAssertionDocument
inside the principal. This is specially useful when re-playing a signed assertion.
The returned document is the one that was generated parsing the SAML response received by the Keycloak server.
This setting is OPTIONAL and its default value is false (the document is not saved inside the principal).
Service Provider Keys and Key Elements
If the IdP requires that the client application (or SP) sign all of its requests and/or if the IdP will encrypt assertions, you must define the keys used to do this.
For client-signed documents you must define both the private and public key or certificate that is used to sign documents.
For encryption, you only have to define the private key that is used to decrypt it.
There are two ways to describe your keys.
They can be stored within a Java KeyStore or you can copy/paste the keys directly within keycloak-saml.xml
in the PEM format.
<Keys>
<Key signing="true" >
...
</Key>
</Keys>
The Key
element has two optional attributes signing
and encryption
.
When set to true these tell the adapter what the key will be used for.
If both attributes are set to true, then the key will be used for both signing documents and decrypting encrypted assertions.
You must set at least one of these attributes to true.
KeyStore element
Within the Key
element you can load your keys and certificates from a Java Keystore. This is declared within
a KeyStore
element.
<Keys>
<Key signing="true" >
<KeyStore resource="/WEB-INF/keystore.jks" password="store123">
<PrivateKey alias="myPrivate" password="test123"/>
<Certificate alias="myCertAlias"/>
</KeyStore>
</Key>
</Keys>
Here are the XML config attributes that are defined with the KeyStore
element.
- file
-
File path to the key store. This option is OPTIONAL. The file or resource attribute must be set.
- resource
-
WAR resource path to the KeyStore.
This is a path used in method call to ServletContext.getResourceAsStream(). This option is OPTIONAL. The file or resource attribute must be set. - password
-
The password of the KeyStore. This option is REQUIRED.
If you are defining keys that the SP will use to sign document, you must also specify references to your private keys
and certificates within the Java KeyStore.
The PrivateKey
and Certificate
elements in the above example define an alias
that points to the key or cert
within the keystore. Keystores require an additional password to access private keys.
In the PrivateKey
element you must define this password within a password
attribute.
Key PEMS
Within the Key
element you declare your keys and certificates directly using the sub elements
PrivateKeyPem
, PublicKeyPem
, and CertificatePem
.
The values contained in these elements must conform to the PEM key format.
You usually use this option if you are generating keys using openssl
or similar command line tool.
<Keys>
<Key signing="true">
<PrivateKeyPem>
2341251234AB31234==231BB998311222423522334
</PrivateKeyPem>
<CertificatePem>
211111341251234AB31234==231BB998311222423522334
</CertificatePem>
</Key>
</Keys>
SP PrincipalNameMapping element
This element is optional.
When creating a Java Principal object that you obtain from methods such as HttpServletRequest.getUserPrincipal()
, you can define what name is returned by the Principal.getName()
method.
<SP ...>
<PrincipalNameMapping policy="FROM_NAME_ID"/>
</SP>
<SP ...>
<PrincipalNameMapping policy="FROM_ATTRIBUTE" attribute="email" />
</SP>
The policy
attribute defines the policy used to populate this value.
The possible values for this attribute are:
- FROM_NAME_ID
-
This policy just uses whatever the SAML subject value is. This is the default setting
- FROM_ATTRIBUTE
-
This will pull the value from one of the attributes declared in the SAML assertion received from the server.
You’ll need to specify the name of the SAML assertion attribute to use within theattribute
XML attribute.
RoleIdentifiers Element
The RoleIdentifiers
element defines what SAML attributes within the assertion received from the user should be used
as role identifiers within the Java EE Security Context for the user.
<RoleIdentifiers>
<Attribute name="Role"/>
<Attribute name="member"/>
<Attribute name="memberOf"/>
</RoleIdentifiers>
By default Role
attribute values are converted to Java EE roles.
Some IdPs send roles using a member
or memberOf
attribute assertion.
You can define one or more Attribute
elements to specify which SAML attributes must be converted into roles.
RoleMappingsProvider Element
The RoleMappingsProvider
is an optional element that allows for the specification of the id and configuration of the
org.keycloak.adapters.saml.RoleMappingsProvider
SPI implementation that is to be used by the SAML adapter.
When Keycloak is used as the IDP, it is possible to use the built in role mappers to map any roles before adding them to the
SAML assertion. However, the SAML adapters can be used to send SAML requests to third party IDPs and in this case it might be
necessary to map the roles extracted from the assertion into a different set of roles as required by the SP. The
RoleMappingsProvider
SPI allows for the configuration of pluggable role mappers that can be used to perform the necessary
mappings.
The configuration of the provider looks as follows:
...
<RoleIdentifiers>
...
</RoleIdentifiers>
<RoleMappingsProvider id="properties-based-role-mapper">
<Property name="properties.resource.location" value="/WEB-INF/role-mappings.properties"/>
</RoleMappingsProvider>
<IDP>
...
</IDP>
The id
attribute identifies which of the installed providers is to be used. The Property
sub-element can be used multiple times
to specify configuration properties for the provider.
Properties Based Role Mappings Provider
Keycloak includes a RoleMappingsProvider
implementation that performs the role mappings using a properties
file. This
provider is identified by the id properties-based-role-mapper
and is implemented by the org.keycloak.adapters.saml.PropertiesBasedRoleMapper
class.
This provider relies on two configuration properties that can be used to specify the location of the properties
file
that will be used. First, it checks if the properties.file.location
property has been specified, using the configured
value to locate the properties
file in the filesystem. If the configured file is not located, the provider throws a
RuntimeException
. The following snippet shows an example of provider using the properties.file.configuration
option to load the roles.properties
file from the /opt/mappers/
directory in the filesystem:
<RoleMappingsProvider id="properties-based-role-mapper">
<Property name="properties.file.location" value="/opt/mappers/roles.properties"/>
</RoleMappingsProvider>
If the properties.file.location
configuration has not been set, the provider checks the properties.resource.location
property, using the configured value to load the properties
file from the WAR
resource. If this configuration property is
also not present, the provider attempts to load the file from /WEB-INF/role-mappings.properties
by default. Failure to load the file
from the resource will result in the provider throwing a RuntimeException
. The following snippet shows an example of provider
using the properties.resource.location
to load the roles.properties
file from the application’s /WEB-INF/conf/
directory:
<RoleMappingsProvider id="properties-based-role-mapper">
<Property name="properties.resource.location" value="/WEB-INF/conf/roles.properties"/>
</RoleMappingsProvider>
The properties
file can contain both roles and principals as keys, and a list of zero or more roles separated by comma
as values. When invoked, the implementation iterates through the set of roles that were extracted from the assertion and checks,
for each role, if a mapping exists. If the role maps to an empty role, it is discarded. If it maps to a set of one ore more
different roles, then these roles are set in the result set. If no mapping is found for the role then it is included as is
in the result set.
Once the roles have been processed, the implementation checks if the principal extracted from the assertion contains an entry
properties
file. If a mapping for the principal exists, any roles listed as value are added to the result set. This
allows the assignment of extra roles to a principal.
As an example, let’s assume the provider has been configured with the following properties file:
roleA=roleX,roleY
roleB=
kc_user=roleZ
If the principal kc_user
is extracted from the assertion with roles roleA
, roleB
and roleC
, the final set of roles
assigned to the principal will be roleC
, roleX
, roleY
and roleZ
because roleA
is being mapped into both roleX
and roleY
, roleB
was mapped into an empty role — thus being discarded, roleC
is used as is and finally an additional role
was added to the kc_user
principal (roleZ
).
Adding Your Own Role Mappings Provider
To add a custom role mappings provider one simply needs to implement the org.keycloak.adapters.saml.RoleMappingsProvider
SPI.
For more details see the SAML Role Mappings SPI
section in Server Developer Guide.
IDP Element
Everything in the IDP element describes the settings for the identity provider (authentication server) the SP is communicating with.
<IDP entityID="idp"
signaturesRequired="true"
signatureAlgorithm="RSA_SHA1"
signatureCanonicalizationMethod="http://www.w3.org/2001/10/xml-exc-c14n#">
...
</IDP>
Here are the attribute config options you can specify within the IDP
element declaration.
- entityID
-
This is the issuer ID of the IDP. This setting is REQUIRED.
- signaturesRequired
-
If set to
true
, the client adapter will sign every document it sends to the IDP.
Also, the client will expect that the IDP will be signing any documents sent to it.
This switch sets the default for all request and response types, but you will see later that you have some fine grain control over this.
This setting is OPTIONAL and will default tofalse
. - signatureAlgorithm
-
This is the signature algorithm that the IDP expects signed documents to use.
Allowed values are:RSA_SHA1
,RSA_SHA256
,RSA_SHA512
, andDSA_SHA1
.
This setting is OPTIONAL
and defaults toRSA_SHA256
. - signatureCanonicalizationMethod
-
This is the signature canonicalization method that the IDP expects signed documents to use. This setting is OPTIONAL.
The default value ishttp://www.w3.org/2001/10/xml-exc-c14n#
and should be good for most IDPs. - metadataUrl
-
The URL used to retrieve the IDP metadata, currently this is only used to pick up signing and encryption keys periodically which allow cycling of these keys on the IDP without manual changes on the SP side.
IDP AllowedClockSkew sub element
The AllowedClockSkew
optional sub element defines the allowed clock skew between IDP and SP.
The default value is 0.
<AllowedClockSkew unit="MILLISECONDS">3500</AllowedClockSkew>
- unit
-
It is possible to define the time unit attached to the value for this element.
Allowed values are MICROSECONDS, MILLISECONDS, MINUTES, NANOSECONDS and SECONDS.
This is OPTIONAL.
The default value isSECONDS
.
IDP SingleSignOnService sub element
The SingleSignOnService
sub element defines the login SAML endpoint of the IDP.
The client adapter will send requests
to the IDP formatted via the settings within this element when it wants to login.
<SingleSignOnService signRequest="true"
validateResponseSignature="true"
requestBinding="post"
bindingUrl="url"/>
Here are the config attributes you can define on this element:
- signRequest
-
Should the client sign authn requests? This setting is OPTIONAL.
Defaults to whatever the IDPsignaturesRequired
element value is. - validateResponseSignature
-
Should the client expect the IDP to sign the assertion response document sent back from an auhtn request?
This setting OPTIONAL. Defaults to whatever the IDPsignaturesRequired
element value is. - requestBinding
-
This is the SAML binding type used for communicating with the IDP. This setting is OPTIONAL.
The default value isPOST
, but you can set it toREDIRECT
as well. - responseBinding
-
SAML allows the client to request what binding type it wants authn responses to use.
The values of this can bePOST
orREDIRECT
. This setting is OPTIONAL.
The default is that the client will not request a specific binding type for responses. - assertionConsumerServiceUrl
-
URL of the assertion consumer service (ACS) where the IDP login service should send responses to.
This setting is OPTIONAL. By default it is unset, relying on the configuration in the IdP.
When set, it must end in/saml
, e.g.http://sp.domain.com/my/endpoint/for/saml
. The value
of this property is sent inAssertionConsumerServiceURL
attribute of SAMLAuthnRequest
message.
This property is typically accompanied by theresponseBinding
attribute. - bindingUrl
-
This is the URL for the IDP login service that the client will send requests to. This setting is REQUIRED.
IDP SingleLogoutService sub element
The SingleLogoutService
sub element defines the logout SAML endpoint of the IDP. The client adapter will send requests
to the IDP formatted via the settings within this element when it wants to logout.
<SingleLogoutService validateRequestSignature="true"
validateResponseSignature="true"
signRequest="true"
signResponse="true"
requestBinding="redirect"
responseBinding="post"
postBindingUrl="posturl"
redirectBindingUrl="redirecturl">
- signRequest
-
Should the client sign logout requests it makes to the IDP? This setting is OPTIONAL.
Defaults to whatever the IDPsignaturesRequired
element value is. - signResponse
-
Should the client sign logout responses it sends to the IDP requests? This setting is OPTIONAL.
Defaults to whatever the IDPsignaturesRequired
element value is. - validateRequestSignature
-
Should the client expect signed logout request documents from the IDP? This setting is OPTIONAL. Defaults to whatever the IDP
signaturesRequired
element value is. - validateResponseSignature
-
Should the client expect signed logout response documents from the IDP? This setting is OPTIONAL. Defaults to whatever the IDP
signaturesRequired
element value is. - requestBinding
-
This is the SAML binding type used for communicating SAML requests to the IDP. This setting is OPTIONAL.
The default value isPOST
, but you can set it to REDIRECT as well. - responseBinding
-
This is the SAML binding type used for communicating SAML responses to the IDP. The values of this can be
POST
orREDIRECT
. This setting is OPTIONAL.
The default value isPOST
, but you can set it toREDIRECT
as well. - postBindingUrl
-
This is the URL for the IDP’s logout service when using the POST binding. This setting is REQUIRED if using the
POST
binding. - redirectBindingUrl
-
This is the URL for the IDP’s logout service when using the REDIRECT binding. This setting is REQUIRED if using the REDIRECT binding.
IDP Keys sub element
The Keys sub element of IDP is only used to define the certificate or public key to use to verify documents signed by the IDP.
It is defined in the same way as the SP’s Keys element.
But again, you only have to define one certificate or public key reference. Note that, if both IDP and SP are realized by
Keycloak server and adapter, respectively, there is no need to specify the keys for signature validation, see below.
It is possible to configure SP to obtain public keys for IDP signature validation
from published certificates automatically, provided both SP and IDP are
implemented by Keycloak.
This is done by removing all declarations of signature validation keys in Keys
sub element. If the Keys sub element would then remain empty, it can be omitted
completely. The keys are then automatically obtained by SP from SAML descriptor,
location of which is derived from SAML endpoint URL specified in the
IDP SingleSignOnService sub element.
Settings of the HTTP client that is used for SAML descriptor retrieval usually
needs no additional configuration, however it can be configured in the
IDP HttpClient sub element.
It is also possible to specify multiple keys for signature verification. This is done by declaring multiple Key elements
within Keys sub element that have signing
attribute set to true
.
This is useful for example in situation when the IDP signing keys are rotated: There is
usually a transition period when new SAML protocol messages and assertions are signed
with the new key but those signed by previous key should still be accepted.
It is not possible to configure Keycloak to both obtain the keys
for signature verification automatically and define additional static signature
verification keys.
<IDP entityID="idp">
...
<Keys>
<Key signing="true">
<KeyStore resource="/WEB-INF/keystore.jks" password="store123">
<Certificate alias="demo"/>
</KeyStore>
</Key>
</Keys>
</IDP>
IDP HttpClient sub element
The HttpClient
optional sub element defines the properties of HTTP client used
for automatic obtaining of certificates containing public keys for IDP signature
verification via SAML descriptor of the IDP when
enabled.
<HttpClient connectionPoolSize="10"
disableTrustManager="false"
allowAnyHostname="false"
clientKeystore="classpath:keystore.jks"
clientKeystorePassword="pwd"
truststore="classpath:truststore.jks"
truststorePassword="pwd"
proxyUrl="http://proxy/" />
- connectionPoolSize
-
This config option defines how many connections to the Keycloak server should be pooled.
This is OPTIONAL.
The default value is10
. - disableTrustManager
-
If the Keycloak server requires HTTPS and this config option is set to
true
you do not have to specify a truststore.
This setting should only be used during development and never in production as it will disable verification of SSL certificates.
This is OPTIONAL.
The default value isfalse
. - allowAnyHostname
-
If the Keycloak server requires HTTPS and this config option is set to
true
the Keycloak server’s certificate is validated via the truststore,
but host name validation is not done.
This setting should only be used during development and never in production
as it will partly disable verification of SSL certificates.
This seting may be useful in test environments. This is OPTIONAL.
The default value isfalse
. - truststore
-
The value is the file path to a truststore file.
If you prefix the path withclasspath:
, then the truststore will be obtained from the deployment’s classpath instead.
Used for outgoing HTTPS communications to the Keycloak server.
Client making HTTPS requests need a way to verify the host of the server they are talking to.
This is what the trustore does.
The keystore contains one or more trusted host certificates or certificate authorities.
You can create this truststore by extracting the public certificate of the Keycloak server’s SSL keystore.
This is REQUIRED unlessdisableTrustManager
istrue
. - truststorePassword
-
Password for the truststore.
This is REQUIRED iftruststore
is set and the truststore requires a password. - clientKeystore
-
This is the file path to a keystore file.
This keystore contains client certificate for two-way SSL when the adapter makes HTTPS requests to the Keycloak server.
This is OPTIONAL. - clientKeystorePassword
-
Password for the client keystore and for the client’s key.
This is REQUIRED ifclientKeystore
is set. - proxyUrl
-
URL to HTTP proxy to use for HTTP connections.
This is OPTIONAL.
3.1.2. JBoss EAP/WildFly Adapter
To be able to secure WAR apps deployed on JBoss EAP or WildFly, you must install and configure the Keycloak SAML Adapter Subsystem.
You then provide a keycloak config, /WEB-INF/keycloak-saml.xml
file in your WAR and change the auth-method to KEYCLOAK-SAML within web.xml.
Both methods are described in this section.
Adapter Installation
Each adapter is a separate download on the Keycloak download site.
We only test and maintain adapter with the most recent version of WildFly available upon the release. Once new version of WildFly is released, the current adapters become deprecated and support for them will be removed after next WildFly release. The other alternative is to switch your applications from WildFly to the JBoss EAP, as the JBoss EAP adapter is supported for much longer period. |
Install on WildFly 9 or newer or on JBoss EAP 7:
$ cd $WILDFLY_HOME
$ unzip keycloak-saml-wildfly-adapter-dist.zip
Install on JBoss EAP 6.x:
$ cd $JBOSS_HOME
$ unzip keycloak-saml-eap6-adapter-dist.zip
These zip files create new JBoss Modules specific to the WildFly/JBoss EAP SAML Adapter within your WildFly or JBoss EAP distro.
After adding the modules, you must then enable the Keycloak SAML Subsystem within your app server’s server configuration: domain.xml
or standalone.xml
.
There is a CLI script that will help you modify your server configuration.
Start the server and run the script from the server’s bin directory:
WildFly 11 or newer
$ cd $JBOSS_HOME
$ ./bin/jboss-cli.sh -c --file=bin/adapter-elytron-install-saml.cli
WildFly 10 and older
$ cd $JBOSS_HOME
$ /bin/boss-cli.sh -c --file=bin/adapter-install-saml.cli
It is possible to use the legacy non-Elytron adapter on WildFly 11 or newer as well, meaning you can use adapter-install-saml.cli even on those versions. However, we recommend to use the newer Elytron adapter. |
The script will add the extension, subsystem, and optional security-domain as described below.
<server xmlns="urn:jboss:domain:1.4">
<extensions>
<extension module="org.keycloak.keycloak-saml-adapter-subsystem"/>
...
</extensions>
<profile>
<subsystem xmlns="urn:jboss:domain:keycloak-saml:1.1"/>
...
</profile>
The keycloak
security domain should be used with EJBs and other components when you need the security context created
in the secured web tier to be propagated to the EJBs (other EE component) you are invoking.
Otherwise this configuration is optional.
<server xmlns="urn:jboss:domain:1.4">
<subsystem xmlns="urn:jboss:domain:security:1.2">
<security-domains>
...
<security-domain name="keycloak">
<authentication>
<login-module code="org.keycloak.adapters.jboss.KeycloakLoginModule"
flag="required"/>
</authentication>
</security-domain>
</security-domains>
The security context is propagated to the EJB tier automatically.
JBoss SSO
WildFly has built-in support for single sign-on for web applications deployed to the same WildFly
instance. This should not be enabled when using Keycloak.
3.1.3. Installing JBoss EAP Adapter from an RPM
Install the EAP 7 Adapters from an RPM:
With Red Hat Enterprise Linux 7, the term channel was replaced with the term repository. In these instructions only the term repository is used. |
You must subscribe to the JBoss EAP 7 repository before you can install the EAP 7 adapters from an RPM.
Prerequisites
-
Ensure that your Red Hat Enterprise Linux system is registered to your account using Red Hat Subscription Manager. For more information see the Red Hat Subscription Management documentation.
-
If you are already subscribed to another JBoss EAP repository, you must unsubscribe from that repository first.
For Red Hat Enterprise Linux 6, 7: Using Red Hat Subscription Manager, subscribe to the WildFly 20 repository using the following command. Replace <RHEL_VERSION> with either 6 or 7 depending on your Red Hat Enterprise Linux version.
$ sudo subscription-manager repos --enable=jb-eap-7-for-rhel-<RHEL_VERSION>-server-rpms
For Red Hat Enterprise Linux 8: Using Red Hat Subscription Manager, subscribe to the WildFly 20 repository using the following command:
$ sudo subscription-manager repos --enable=jb-eap-20-for-rhel-8-x86_64-rpms --enable=rhel-8-for-x86_64-baseos-rpms --enable=rhel-8-for-x86_64-appstream-rpms
Install the EAP 7 adapters for SAML using the following command:
$ sudo yum install eap7-keycloak-saml-adapter-sso7_4
or use following one for Red Hat Enterprise Linux 8:
$ sudo dnf install eap7-keycloak-adapter-sso7_4
The default EAP_HOME path for the RPM installation is /opt/rh/eap7/root/usr/share/wildfly. |
Run the appropriate module installation script.
For the SAML module, enter the following command:
$ $EAP_HOME/bin/jboss-cli.sh -c --file=$EAP_HOME/bin/adapter-install-saml.cli
Your installation is complete.
Install the EAP 6 Adapters from an RPM:
With Red Hat Enterprise Linux 7, the term channel was replaced with the term repository. In these instructions only the term repository is used. |
You must subscribe to the JBoss EAP 6 repository before you can install the EAP 6 adapters from an RPM.
Prerequisites
-
Ensure that your Red Hat Enterprise Linux system is registered to your account using Red Hat Subscription Manager. For more information see the Red Hat Subscription Management documentation.
-
If you are already subscribed to another JBoss EAP repository, you must unsubscribe from that repository first.
Using Red Hat Subscription Manager, subscribe to the JBoss EAP 6 repository using the following command. Replace <RHEL_VERSION> with either 6 or 7 depending on your Red Hat Enterprise Linux version.
$ sudo subscription-manager repos --enable=jb-eap-6-for-rhel-<RHEL_VERSION>-server-rpms
Install the EAP 6 adapters for SAML using the following command:
$ sudo yum install keycloak-saml-adapter-sso7_4-eap6
The default EAP_HOME path for the RPM installation is /opt/rh/eap6/root/usr/share/wildfly. |
Run the appropriate module installation script.
For the SAML module, enter the following command:
$ $EAP_HOME/bin/jboss-cli.sh -c --file=$EAP_HOME/bin/adapter-install-saml.cli
Your installation is complete.
Per WAR Configuration
This section describes how to secure a WAR directly by adding config and editing files within your WAR package.
The first thing you must do is create a keycloak-saml.xml
adapter config file within the WEB-INF
directory of your WAR.
The format of this config file is described in the General Adapter Config section.
Next you must set the auth-method
to KEYCLOAK-SAML
in web.xml
.
You also have to use standard servlet security to specify role-base constraints on your URLs.
Here’s an example web.xml file:
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<module-name>customer-portal</module-name>
<security-constraint>
<web-resource-collection>
<web-resource-name>Admins</web-resource-name>
<url-pattern>/admin/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>Customers</web-resource-name>
<url-pattern>/customers/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>user</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<login-config>
<auth-method>KEYCLOAK-SAML</auth-method>
<realm-name>this is ignored currently</realm-name>
</login-config>
<security-role>
<role-name>admin</role-name>
</security-role>
<security-role>
<role-name>user</role-name>
</security-role>
</web-app>
All standard servlet settings except the auth-method
setting.
Securing WARs via Keycloak SAML Subsystem
You do not have to crack open a WAR to secure it with Keycloak.
Alternatively, you can externally secure it via the Keycloak SAML Adapter Subsystem.
While you don’t have to specify KEYCLOAK-SAML as an auth-method
, you still have to define the security-constraints
in web.xml
.
You do not, however, have to create a WEB-INF/keycloak-saml.xml
file.
This metadata is instead defined within the XML in your server’s domain.xml
or standalone.xml
subsystem configuration section.
<extensions>
<extension module="org.keycloak.keycloak-saml-adapter-subsystem"/>
</extensions>
<profile>
<subsystem xmlns="urn:jboss:domain:keycloak-saml:1.1">
<secure-deployment name="WAR MODULE NAME.war">
<SP entityID="APPLICATION URL">
...
</SP>
</secure-deployment>
</subsystem>
</profile>
The secure-deployment
name
attribute identifies the WAR you want to secure.
Its value is the module-name
defined in web.xml
with .war
appended.
The rest of the configuration uses the same XML syntax as keycloak-saml.xml
configuration defined in General Adapter Config.
An example configuration:
<subsystem xmlns="urn:jboss:domain:keycloak-saml:1.1">
<secure-deployment name="saml-post-encryption.war">
<SP entityID="http://localhost:8080/sales-post-enc/"
sslPolicy="EXTERNAL"
nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
logoutPage="/logout.jsp"
forceAuthentication="false">
<Keys>
<Key signing="true" encryption="true">
<KeyStore resource="/WEB-INF/keystore.jks" password="store123">
<PrivateKey alias="http://localhost:8080/sales-post-enc/" password="test123"/>
<Certificate alias="http://localhost:8080/sales-post-enc/"/>
</KeyStore>
</Key>
</Keys>
<PrincipalNameMapping policy="FROM_NAME_ID"/>
<RoleIdentifiers>
<Attribute name="Role"/>
</RoleIdentifiers>
<IDP entityID="idp">
<SingleSignOnService signRequest="true"
validateResponseSignature="true"
requestBinding="POST"
bindingUrl="http://localhost:8080/auth/realms/saml-demo/protocol/saml"/>
<SingleLogoutService
validateRequestSignature="true"
validateResponseSignature="true"
signRequest="true"
signResponse="true"
requestBinding="POST"
responseBinding="POST"
postBindingUrl="http://localhost:8080/auth/realms/saml-demo/protocol/saml"
redirectBindingUrl="http://localhost:8080/auth/realms/saml-demo/protocol/saml"/>
<Keys>
<Key signing="true" >
<KeyStore resource="/WEB-INF/keystore.jks" password="store123">
<Certificate alias="saml-demo"/>
</KeyStore>
</Key>
</Keys>
</IDP>
</SP>
</secure-deployment>
</subsystem>
3.1.4. Tomcat SAML adapters
To be able to secure WAR apps deployed on Tomcat 7, 8 and 9 you must install the Keycloak Tomcat 7 SAML adapter or Keycloak Tomcat SAML adapter into your Tomcat installation.
You then have to provide some extra configuration in each WAR you deploy to Tomcat.
Let’s go over these steps.
Adapter Installation
Adapters are no longer included with the appliance or war distribution.
Each adapter is a separate download on the Keycloak download site.
They are also available as a maven artifact.
You must unzip the adapter distro into Tomcat’s lib/
directory.
Including adapter’s jars within your WEB-INF/lib directory will not work! The Keycloak SAML adapter is implemented as
a Valve and valve code must reside in Tomcat’s main lib/ directory.
Install on Tomcat 7:
$ cd $TOMCAT_HOME/lib
$ unzip keycloak-saml-tomcat7-adapter-dist.zip
Install on Tomcat 8 or 9:
$ cd $TOMCAT_HOME/lib
$ unzip keycloak-saml-tomcat-adapter-dist.zip
Per WAR Configuration
This section describes how to secure a WAR directly by adding config and editing files within your WAR package.
The first thing you must do is create a META-INF/context.xml
file in your WAR package.
This is a Tomcat specific config file and you must define a Keycloak specific Valve.
<Context path="/your-context-path">
<Valve className="org.keycloak.adapters.saml.tomcat.SamlAuthenticatorValve"/>
</Context>
Next you must create a keycloak-saml.xml
adapter config file within the WEB-INF
directory of your WAR.
The format of this config file is described in the General Adapter Config section.
Finally you must specify both a login-config
and use standard servlet security to specify role-base constraints on your URLs.
Here’s an example:
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<module-name>customer-portal</module-name>
<security-constraint>
<web-resource-collection>
<web-resource-name>Customers</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>user</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>this is ignored currently</realm-name>
</login-config>
<security-role>
<role-name>admin</role-name>
</security-role>
<security-role>
<role-name>user</role-name>
</security-role>
</web-app>
If the keycloak-saml.xml
does not explicitly set assertionConsumerServiceUrl
, the SAML adapter will implicitly listen for SAML assertions at the location /my-context-path/saml
. This has to match Master SAML Processing URL
in the IDP realm/client settings, e.g. http://sp.domain.com/my-context-path/saml
. If not, Tomcat will probably redirect infinitely to the IDP login service, as it does not receive the SAML assertion after the user logged in.
3.1.5. Jetty SAML Adapters
To be able to secure WAR apps deployed on Jetty you must install the Keycloak Jetty 9.x SAML adapter into your Jetty installation.
You then have to provide some extra configuration in each WAR you deploy to Jetty.
Let’s go over these steps.
Jetty 9 Adapter Installation
Keycloak has a separate SAML adapter for Jetty 9.x.
You then have to provide some extra configuration in each WAR you deploy to Jetty.
Let’s go over these steps.
Adapters are no longer included with the appliance or war distribution. Each adapter is a separate download on the Keycloak download site.
They are also available as a maven artifact.
You must unzip the Jetty 9.x distro into Jetty 9.x’s root directory.
Including adapter’s jars within your WEB-INF/lib directory will not work!
$ cd $JETTY_HOME
$ unzip keycloak-saml-jetty92-adapter-dist.zip
Next, you will have to enable the keycloak module for your jetty.base.
$ cd your-base
$ java -jar $JETTY_HOME/start.jar --add-to-startd=keycloak
Jetty 9 Per WAR Configuration
This section describes how to secure a WAR directly by adding config and editing files within your WAR package.
The first thing you must do is create a WEB-INF/jetty-web.xml
file in your WAR package.
This is a Jetty specific config file and you must define a Keycloak specific authenticator within it.
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
<Get name="securityHandler">
<Set name="authenticator">
<New class="org.keycloak.adapters.saml.jetty.KeycloakSamlAuthenticator">
</New>
</Set>
</Get>
</Configure>
Next you must create a keycloak-saml.xml
adapter config file within the WEB-INF
directory of your WAR.
The format of this config file is described in the General Adapter Config section.
Finally you must specify both a login-config
and use standard servlet security to specify role-base constraints on your URLs.
Here’s an example:
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<module-name>customer-portal</module-name>
<security-constraint>
<web-resource-collection>
<web-resource-name>Customers</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>user</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>this is ignored currently</realm-name>
</login-config>
<security-role>
<role-name>admin</role-name>
</security-role>
<security-role>
<role-name>user</role-name>
</security-role>
</web-app>
3.1.6. Java Servlet Filter Adapter
If you want to use SAML with a Java servlet application that doesn’t have an adapter for that servlet platform, you can
opt to use the servlet filter adapter that Keycloak has.
This adapter works a little differently than the other adapters.
You still have to specify a /WEB-INF/keycloak-saml.xml
file as defined in
the General Adapter Config section, but
you do not define security constraints in web.xml.
Instead you define a filter mapping using the Keycloak servlet filter adapter to secure the url patterns you want to secure.
Backchannel logout works a bit differently than the standard adapters. Instead of invalidating the http session it instead marks the session ID as logged out. There’s just no way of arbitrarily invalidating an http session based on a session ID. |
Backchannel logout does not currently work when you have a clustered application that uses the SAML filter. |
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<module-name>customer-portal</module-name>
<filter>
<filter-name>Keycloak Filter</filter-name>
<filter-class>org.keycloak.adapters.saml.servlet.SamlFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>Keycloak Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
The Keycloak filter has the same configuration parameters available as the other adapters except you must
define them as filter init params instead of context params.
You can define multiple filter mappings if you have various different secure and unsecure url patterns.
You must have a filter mapping that covers /saml .This mapping covers all server callbacks. |
When registering SPs with an IdP, you must register http[s]://hostname/{context-root}/saml
as your Assert Consumer Service URL and Single Logout Service URL.
To use this filter, include this maven artifact in your WAR poms:
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-servlet-filter-adapter</artifactId>
<version>11.0.3</version>
</dependency>
In order to use Multi Tenancy the keycloak.config.resolver
parameter should be passed as a filter parameter.
<filter>
<filter-name>Keycloak Filter</filter-name>
<filter-class>org.keycloak.adapters.saml.servlet.SamlFilter</filter-class>
<init-param>
<param-name>keycloak.config.resolver</param-name>
<param-value>example.SamlMultiTenantResolver</param-value>
</init-param>
</filter>
3.1.7. Registering with an Identity Provider
For each servlet-based adapter, the endpoint you register for the assert consumer service URL and single logout service
must be the base URL of your servlet application with /saml
appended to it, that is, https://example.com/contextPath/saml
.
3.1.8. Logout
There are multiple ways you can logout from a web application.
For Java EE servlet containers, you can call HttpServletRequest.logout()
. For any other browser application, you can point
the browser at any url of your web application that has a security constraint and pass in a query parameter GLO, i.e. http://myapp?GLO=true
.
This will log you out if you have an SSO session with your browser.
Logout in Clustered Environment
Internally, the SAML adapter stores a mapping between the SAML session index, principal name (when known), and HTTP session ID.
This mapping can be maintained in JBoss application server family (WildFly 10/11, EAP 6/7) across cluster for distributable
applications. As a precondition, the HTTP sessions need to be distributed across cluster (i.e. application is marked with
<distributable/>
tag in application’s web.xml
).
To enable the functionality, add the following section to your /WEB_INF/web.xml
file:
For EAP 7, WildFly 10/11:
<context-param>
<param-name>keycloak.sessionIdMapperUpdater.classes</param-name>
<param-value>org.keycloak.adapters.saml.wildfly.infinispan.InfinispanSessionCacheIdMapperUpdater</param-value>
</context-param>
For EAP 6:
<context-param>
<param-name>keycloak.sessionIdMapperUpdater.classes</param-name>
<param-value>org.keycloak.adapters.saml.jbossweb.infinispan.InfinispanSessionCacheIdMapperUpdater</param-value>
</context-param>
If the session cache of the deployment is named deployment-cache
, the cache used for SAML mapping will be named
as deployment-cache.ssoCache
. The name of the cache can be overridden by a context parameter
keycloak.sessionIdMapperUpdater.infinispan.cacheName
. The cache container containing the cache will be the same as
the one containing the deployment session cache, but can be overridden by a context parameter
keycloak.sessionIdMapperUpdater.infinispan.containerName
.
By default, the configuration of the SAML mapping cache will be derived from session cache. The configuration can
be manually overridden in cache configuration section of the server just the same as other caches.
Currently, to provide reliable service, it is recommended to use replicated cache for the SAML session cache.
Using distributed cache may lead to results where the SAML logout request would land to a node with no access
to SAML session index to HTTP session mapping which would lead to unsuccessful logout.
Logout in Cross DC Scenario
The cross DC scenario only applies to WildFly 10 and higher, and EAP 7 and higher.
Special handling is needed for handling sessions that span multiple data centers. Imagine the following scenario:
-
Login requests are handled within cluster in data center 1.
-
Admin issues logout request for a particular SAML session, the request lands in data center 2.
The data center 2 has to log out all sessions that are present in data center 1 (and all other data centers that
share HTTP sessions).
To cover this case, the SAML session cache described above needs to be replicated
not only within individual clusters but across all the data centers e.g.
via standalone Infinispan/JDG server:
-
A cache has to be added to the standalone Infinispan/JDG server.
-
The cache from previous item has to be added as a remote store for the respective SAML session cache.
Once remote store is found to be present on SAML session cache during deployment, it is watched for changes
and the local SAML session cache is updated accordingly.
3.1.9. Obtaining Assertion Attributes
After a successful SAML login, your application code may want to obtain attribute values passed with the SAML assertion.
HttpServletRequest.getUserPrincipal()
returns a Principal
object that you can typecast into a Keycloak specific class
called org.keycloak.adapters.saml.SamlPrincipal
.
This object allows you to look at the raw assertion and also has convenience functions to look up attribute values.
package org.keycloak.adapters.saml;
public class SamlPrincipal implements Serializable, Principal {
/**
* Get full saml assertion
*
* @return
*/
public AssertionType getAssertion() {
...
}
/**
* Get SAML subject sent in assertion
*
* @return
*/
public String getSamlSubject() {
...
}
/**
* Subject nameID format
*
* @return
*/
public String getNameIDFormat() {
...
}
@Override
public String getName() {
...
}
/**
* Convenience function that gets Attribute value by attribute name
*
* @param name
* @return
*/
public List<String> getAttributes(String name) {
...
}
/**
* Convenience function that gets Attribute value by attribute friendly name
*
* @param friendlyName
* @return
*/
public List<String> getFriendlyAttributes(String friendlyName) {
...
}
/**
* Convenience function that gets first value of an attribute by attribute name
*
* @param name
* @return
*/
public String getAttribute(String name) {
...
}
/**
* Convenience function that gets first value of an attribute by attribute name
*
*
* @param friendlyName
* @return
*/
public String getFriendlyAttribute(String friendlyName) {
...
}
/**
* Get set of all assertion attribute names
*
* @return
*/
public Set<String> getAttributeNames() {
...
}
/**
* Get set of all assertion friendly attribute names
*
* @return
*/
public Set<String> getFriendlyNames() {
...
}
}
3.1.10. Error Handling
Keycloak has some error handling facilities for servlet based client adapters.
When an error is encountered in authentication, the client adapter will call HttpServletResponse.sendError()
.
You can set up an error-page
within your web.xml
file to handle the error however you want.
The client adapter can throw 400, 401, 403, and 500 errors.
<error-page>
<error-code>403</error-code>
<location>/ErrorHandler</location>
</error-page>
The client adapter also sets an HttpServletRequest
attribute that you can retrieve.
The attribute name is org.keycloak.adapters.spi.AuthenticationError
.
Typecast this object to: org.keycloak.adapters.saml.SamlAuthenticationError
.
This class can tell you exactly what happened.
If this attribute is not set, then the adapter was not responsible for the error code.
public class SamlAuthenticationError implements AuthenticationError {
public static enum Reason {
EXTRACTION_FAILURE,
INVALID_SIGNATURE,
ERROR_STATUS
}
public Reason getReason() {
return reason;
}
public StatusResponseType getStatus() {
return status;
}
}
3.1.11. Troubleshooting
The best way to troubleshoot problems is to turn on debugging for SAML in both the client adapter and Keycloak Server. Using your logging framework, set the log level to DEBUG
for the org.keycloak.saml
package. Turning this on allows you to see the SAML requests and response documents being sent to and from the server.
3.1.12. Multi Tenancy
SAML offers the same functionality as OIDC for Multi Tenancy, meaning that a single target application (WAR) can be secured with multiple Keycloak realms. The realms can be located on the same Keycloak instance or on different instances.
To do this, the application must have multiple keycloak-saml.xml
adapter configuration files.
While you could have multiple instances of your WAR with different adapter configuration files deployed to different context-paths, this may be inconvenient and you may also want to select the realm based on something other than context-path.
Keycloak makes it possible to have a custom config resolver, so you can choose which adapter config is used for each request. In SAML, the configuration is only interesting in the login processing; once the user is logged in, the session is authenticated and it does not matter if the keycloak-saml.xml
returned is different. For that reason, returning the same configuration for the same session is the correct way to go.
To achieve this, create an implementation of org.keycloak.adapters.saml.SamlConfigResolver
. The following example uses the Host
header to locate the proper configuration and load it and the associated elements from the applications’s Java classpath:
package example;
import java.io.InputStream;
import org.keycloak.adapters.saml.SamlConfigResolver;
import org.keycloak.adapters.saml.SamlDeployment;
import org.keycloak.adapters.saml.config.parsers.DeploymentBuilder;
import org.keycloak.adapters.saml.config.parsers.ResourceLoader;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.saml.common.exceptions.ParsingException;
public class SamlMultiTenantResolver implements SamlConfigResolver {
@Override
public SamlDeployment resolve(HttpFacade.Request request) {
String host = request.getHeader("Host");
String realm = null;
if (host.contains("tenant1")) {
realm = "tenant1";
} else if (host.contains("tenant2")) {
realm = "tenant2";
} else {
throw new IllegalStateException("Not able to guess the keycloak-saml.xml to load");
}
InputStream is = getClass().getResourceAsStream("/" + realm + "-keycloak-saml.xml");
if (is == null) {
throw new IllegalStateException("Not able to find the file /" + realm + "-keycloak-saml.xml");
}
ResourceLoader loader = new ResourceLoader() {
@Override
public InputStream getResourceAsStream(String path) {
return getClass().getResourceAsStream(path);
}
};
try {
return new DeploymentBuilder().build(is, loader);
} catch (ParsingException e) {
throw new IllegalStateException("Cannot load SAML deployment", e);
}
}
}
You must also configure which SamlConfigResolver
implementation to use with the keycloak.config.resolver
context-param in your web.xml
:
<web-app>
...
<context-param>
<param-name>keycloak.config.resolver</param-name>
<param-value>example.SamlMultiTenantResolver</param-value>
</context-param>
</web-app>
3.1.13. Migration from older versions
Migrating to 1.9.0
SAML SP Client Adapter Changes
Keycloak SAML SP Client Adapter now requires a specific endpoint, /saml
to be registered with your IdP.
The SamlFilter must also be bound to /saml in addition to any other binding it has.
This had to be done because SAML POST binding would eat the request input stream and this would be really bad for clients that relied on it.
3.2. mod_auth_mellon Apache HTTPD Module
The mod_auth_mellon module is an Apache HTTPD plugin for SAML. If your language/environment supports using Apache HTTPD as a proxy, then you can use mod_auth_mellon to secure your web application with SAML. For more details on this module see the mod_auth_mellon GitHub repo.
To configure mod_auth_mellon you’ll need:
-
An Identity Provider (IdP) entity descriptor XML file, which describes the connection to Keycloak or another SAML IdP
-
An SP entity descriptor XML file, which describes the SAML connections and configuration for the application you are securing.
-
A private key PEM file, which is a text file in the PEM format that defines the private key the application uses to sign documents.
-
A certificate PEM file, which is a text file that defines the certificate for your application.
-
mod_auth_mellon-specific Apache HTTPD module configuration.
If you have already defined and registered the client application within a realm on the Keycloak application server, Keycloak can generate all the files you need except the Apache HTTPD module configuration.
To generate the Apache HTTPD module configuration, complete the following steps:
-
Go to the Installation page of your SAML client and select the Mod Auth Mellon files option.
mod_auth_mellon config download
-
Click Download to download a zip file that contains the XML descriptor and PEM files you need.
3.2.1. Configuring mod_auth_mellon with Keycloak
There are two hosts involved:
-
The host on which Keycloak is running, which will be referred to as $idp_host because Keycloak is a SAML identity provider (IdP).
-
The host on which the web application is running, which will be referred to as $sp_host. In SAML an application using an IdP is called a service provider (SP).
All of the following steps need to performed on $sp_host with root privileges.
Installing the Packages
To install the necessary packages, you will need:
-
Apache Web Server (httpd)
-
Mellon SAML SP add-on module for Apache
-
Tools to create X509 certificates
To install the necessary packages, run this command:
yum install httpd mod_auth_mellon mod_ssl openssl
Creating a Configuration Directory for Apache SAML
It is advisable to keep configuration files related to Apache’s use of SAML in one location.
Create a new directory named saml2 located under the Apache configuration root /etc/httpd:
Configuring the Mellon Service Provider
Configuration files for Apache add-on modules are located in the /etc/httpd/conf.d directory and have a file name extension of .conf. You need to create the /etc/httpd/conf.d/mellon.conf file and place Mellon’s configuration directives in it.
Mellon’s configuration directives can roughly be broken down into two classes of information:
-
Which URLs to protect with SAML authentication
-
What SAML parameters will be used when a protected URL is referenced.
Apache configuration directives typically follow a hierarchical tree structure in the URL space, which are known as locations. You need to specify one or more URL locations for Mellon to protect. You have flexibility in how you add the configuration parameters that apply to each location. You can either add all the necessary parameters to the location block or you can add Mellon parameters to a common location high up in the URL location hierarchy that specific protected locations inherit (or some combination of the two). Since it is common for an SP to operate in the same way no matter which location triggers SAML actions, the example configuration used here places common Mellon configuration directives in the root of the hierarchy and then specific locations to be protected by Mellon can be defined with minimal directives. This strategy avoids duplicating the same parameters for each protected location.
This example has just one protected location: https://$sp_host/private.
To configure the Mellon service provider, complete the following steps:
-
Create the file /etc/httpd/conf.d/mellon.conf with this content:
<Location / >
MellonEnable info
MellonEndpointPath /mellon/
MellonSPMetadataFile /etc/httpd/saml2/mellon_metadata.xml
MellonSPPrivateKeyFile /etc/httpd/saml2/mellon.key
MellonSPCertFile /etc/httpd/saml2/mellon.crt
MellonIdPMetadataFile /etc/httpd/saml2/idp_metadata.xml
</Location>
<Location /private >
AuthType Mellon
MellonEnable auth
Require valid-user
</Location>
Some of the files referenced in the code above are created in later steps. |
3.2.2. Setting the SameSite value for the cookie used by mod_auth_mellon
Browsers are planning to set the default value for the SameSite
attribute for cookies to Lax
. This setting means
that cookies will be sent to applications only if the request originates in the same domain. This behavior can affect
the SAML POST binding which may become non-functional. To preserve full functionality of the mod_auth_mellon module,
we recommend setting the SameSite
value to None
for the cookie created by mod_auth_mellon. Not doing so may result
in an inability to login using Keycloak.
To set the SameSite
value to None
, add the following configuration to <Location / >
tag within your mellon.conf
file.
MellonSecureCookie On
MellonCookieSameSite none
The support for this configuration is available in the mod_auth_mellon module from version 0.16.0.
Creating the Service Provider Metadata
In SAML IdPs and SPs exchange SAML metadata, which is in XML format. The schema for the metadata is a standard, thus assuring participating SAML entities can consume each other’s metadata. You need:
-
Metadata for the IdP that the SP utilizes
-
Metadata describing the SP provided to the IdP
One of the components of SAML metadata is X509 certificates. These certificates are used for two purposes:
-
Sign SAML messages so the receiving end can prove the message originated from the expected party.
-
Encrypt the message during transport (seldom used because SAML messages typically occur on TLS-protected transports)
You can use your own certificates if you already have a Certificate Authority (CA) or you can generate a self-signed certificate. For simplicity in this example a self-signed certificate is used.
Because Mellon’s SP metadata must reflect the capabilities of the installed version of mod_auth_mellon, must be valid SP metadata XML, and must contain an X509 certificate (whose creation can be obtuse unless you are familiar with X509 certificate generation) the most expedient way to produce the SP metadata is to use a tool included in the mod_auth_mellon package (mellon_create_metadata.sh). The generated metadata can always be edited later because it is a text file. The tool also creates your X509 key and certificate.
SAML IdPs and SPs identify themselves using a unique name known as an EntityID. To use the Mellon metadata creation tool you need:
-
The EntityID, which is typically the URL of the SP, and often the URL of the SP where the SP metadata can be retrieved
-
The URL where SAML messages for the SP will be consumed, which Mellon calls the MellonEndPointPath.
To create the SP metadata, complete the following steps:
-
Create a few helper shell variables:
fqdn=`hostname` mellon_endpoint_url="https://${fqdn}/mellon" mellon_entity_id="${mellon_endpoint_url}/metadata" file_prefix="$(echo "$mellon_entity_id" | sed 's/[^A-Za-z.]/_/g' | sed 's/__*/_/g')"
-
Invoke the Mellon metadata creation tool by running this command:
/usr/libexec/mod_auth_mellon/mellon_create_metadata.sh $mellon_entity_id $mellon_endpoint_url
-
Move the generated files to their destination (referenced in the /etc/httpd/conf.d/mellon.conf file created above):
mv ${file_prefix}.cert /etc/httpd/saml2/mellon.crt mv ${file_prefix}.key /etc/httpd/saml2/mellon.key mv ${file_prefix}.xml /etc/httpd/saml2/mellon_metadata.xml
Adding the Mellon Service Provider to the Keycloak Identity Provider
Assumption: The Keycloak IdP has already been installed on the $idp_host.
Keycloak supports multiple tenancy where all users, clients, and so on are grouped in what is called a realm. Each realm is independent of other realms. You can use an existing realm in your Keycloak, but this example shows how to create a new realm called test_realm and use that realm.
All these operations are performed using the Keycloak administration web console. You must have the admin username and password for $idp_host.
To complete the following steps:
-
Open the Admin Console and log on by entering the admin username and password.
After logging into the administration console there will be an existing realm. When Keycloak is first set up a root realm, master, is created by default. Any previously created realms are listed in the upper left corner of the administration console in a drop-down list.
-
From the realm drop-down list select Add realm.
-
In the Name field type
test_realm
and click Create.
Adding the Mellon Service Provider as a Client of the Realm
In Keycloak SAML SPs are known as clients. To add the SP we must be in the Clients section of the realm.
-
Click the Clients menu item on the left and click Create in the upper right corner to create a new client.
Adding the Mellon SP Client
To add the Mellon SP client, complete the following steps:
-
Set the client protocol to SAML. From the Client Protocol drop down list, select saml.
-
Provide the Mellon SP metadata file created above (/etc/httpd/saml2/mellon_metadata.xml). Depending on where your browser is running you might have to copy the SP metadata from $sp_host to the machine on which your browser is running so the browser can find the file.
-
Click Save.
Editing the Mellon SP Client
There are several client configuration parameters we suggest setting:
-
Ensure «Force POST Binding» is On.
-
Add paosResponse to the Valid Redirect URIs list:
-
Copy the postResponse URL in «Valid Redirect URIs» and paste it into the empty add text fields just below the «+».
-
Change «postResponse» to «paosResponse». (The paosResponse URL is needed for SAML ECP.)
-
Click Save at the bottom.
-
Many SAML SPs determine authorization based on a user’s membership in a group. The Keycloak IdP can manage user group information but it does not supply the user’s groups unless the IdP is configured to supply it as a SAML attribute.
To configure the IdP to supply the user’s groups as as a SAML attribute, complete the following steps:
-
Click the Mappers tab of the client.
-
In the upper right corner of the Mappers page, click Create.
-
From the Mapper Type drop-down list select Group list.
-
Set Name to «group list».
-
Set the SAML attribute name to «groups».
-
Click Save.
The remaining steps are performed on $sp_host.
Retrieving the Identity Provider Metadata
Now that you have created the realm on the IdP you need to retrieve the IdP metadata associated with it so the Mellon SP recognizes it. In the /etc/httpd/conf.d/mellon.conf file created previously, the MellonIdPMetadataFile is specified as /etc/httpd/saml2/idp_metadata.xml but until now that file has not existed on $sp_host. To get that file we will retrieve it from the IdP.
-
Retrieve the file from the IdP by substituting $idp_host with the correct value:
curl -k -o /etc/httpd/saml2/idp_metadata.xml https://$idp_host/auth/realms/test_realm/protocol/saml/descriptor
Mellon is now fully configured.
-
To run a syntax check for Apache configuration files:
Configtest is equivalent to the -t argument to apachectl. If the configuration test shows any errors, correct them before proceeding. -
Restart the Apache server:
systemctl restart httpd.service
You have now set up both Keycloak as a SAML IdP in the test_realm and mod_auth_mellon as SAML SP protecting the URL $sp_host/protected (and everything beneath it) by authenticating against the $idp_host
IdP.
4. Docker Registry Configuration
Docker authentication is disabled by default. To enable see Profiles. |
This section describes how you can configure a Docker registry to use Keycloak as its authentication server.
4.1. Docker Registry Configuration File Installation
For users with more advanced Docker registry configurations, it is generally recommended to provide your own registry configuration file. The Keycloak Docker provider supports this mechanism via the Registry Config File Format Option. Choosing this option will generate output similar to the following:
auth: token: realm: http://localhost:8080/auth/realms/master/protocol/docker-v2/auth service: docker-test issuer: http://localhost:8080/auth/realms/master
This output can then be copied into any existing registry config file. See the registry config file specification for more information on how the file should be set up, or start with a basic example.
Don’t forget to configure the rootcertbundle field with the location of the Keycloak realm’s public certificate. The auth configuration will not work without this argument.
|
4.2. Docker Registry Environment Variable Override Installation
Often times it is appropriate to use a simple environment variable override for develop or POC Docker registries. While this approach is usually not recommended for production use, it can be helpful when one requires quick-and-dirty way to stand up a registry. Simply use the Variable Override Format Option from the client installation tab, and an output should appear like the one below:
REGISTRY_AUTH_TOKEN_REALM: http://localhost:8080/auth/realms/master/protocol/docker-v2/auth REGISTRY_AUTH_TOKEN_SERVICE: docker-test REGISTRY_AUTH_TOKEN_ISSUER: http://localhost:8080/auth/realms/master
Don’t forget to configure the REGISTRY_AUTH_TOKEN_ROOTCERTBUNDLE override with the location of the Keycloak realm’s public certificate. The auth configuration will not work without this argument.
|
4.3. Docker Compose YAML File
This installation method is meant to be an easy way to get a docker registry authenticating against a Keycloak server. It is intended for development purposes only and should never be used in a production or production-like environment. |
The zip file installation mechanism provides a quickstart for developers who want to understand how the Keycloak server can interact with the Docker registry. In order to configure:
-
From the desired realm, create a client configuration. At this point you won’t have a Docker registry — the quickstart will take care of that part.
-
Choose the «Docker Compose YAML» option from the installation tab and download the .zip file
-
Unzip the archive to the desired location, and open the directory.
-
Start the Docker registry with
docker-compose up
it is recommended that you configure the Docker registry client in a realm other than ‘master’, since the HTTP Basic auth flow will not present forms. |
Once the above configuration has taken place, and the keycloak server and Docker registry are running, docker authentication should be successful:
[user ~]# docker login localhost:5000 -u $username Password: ******* Login Succeeded
5. Client Registration
In order for an application or service to utilize Keycloak it has to register a client in Keycloak.
An admin can do this through the admin console (or admin REST endpoints), but clients can also register themselves through the Keycloak client
registration service.
The Client Registration Service provides built-in support for Keycloak Client Representations, OpenID Connect Client Meta Data and SAML Entity Descriptors.
The Client Registration Service endpoint is /auth/realms/<realm>/clients-registrations/<provider>
.
The built-in supported providers
are:
-
default — Keycloak Client Representation (JSON)
-
install — Keycloak Adapter Configuration (JSON)
-
openid-connect — OpenID Connect Client Metadata Description (JSON)
-
saml2-entity-descriptor — SAML Entity Descriptor (XML)
The following sections will describe how to use the different providers.
5.1. Authentication
To invoke the Client Registration Services you usually need a token. The token can be a bearer token, an initial access token or a registration access token.
There is an alternative to register new client without any token as well, but then you need to configure Client Registration Policies (see below).
5.1.1. Bearer Token
The bearer token can be issued on behalf of a user or a Service Account. The following permissions are required to invoke the endpoints (see Server Administration Guide for more details):
-
create-client or manage-client — To create clients
-
view-client or manage-client — To view clients
-
manage-client — To update or delete client
If you are using a bearer token to create clients it’s recommend to use a token from a Service Account with only the create-client
role (see Server Administration Guide for more details).
5.1.2. Initial Access Token
The recommended approach to registering new clients is by using initial access tokens.
An initial access token can only be used to create clients and has a configurable expiration as well as a configurable limit on how many clients can be created.
An initial access token can be created through the admin console.
To create a new initial access token first select the realm in the admin console, then click on Realm Settings
in the menu on the left, followed by
Client Registration
in the tabs displayed in the page. Then finally click on Initial Access Tokens
sub-tab.
You will now be able to see any existing initial access tokens. If you have access you can delete tokens that are no longer required. You can only retrieve the
value of the token when you are creating it. To create a new token click on Create
. You can now optionally add how long the token should be valid, also how
many clients can be created using the token. After you click on Save
the token value is displayed.
It is important that you copy/paste this token now as you won’t be able to retrieve it later. If you forget to copy/paste it, then delete the token and create another one.
The token value is used as a standard bearer token when invoking the Client Registration Services, by adding it to the Authorization header in the request.
For example:
Authorization: bearer eyJhbGciOiJSUz...
5.1.3. Registration Access Token
When you create a client through the Client Registration Service the response will include a registration access token.
The registration access token provides access to retrieve the client configuration later, but also to update or delete the client.
The registration access token is included with the request in the same way as a bearer token or initial access token.
Registration access tokens are only valid once, when it’s used the response will include a new token.
If a client was created outside of the Client Registration Service it won’t have a registration access token associated with it.
You can create one through the admin console. This can also be useful if you lose the token for a particular client.
To create a new token find the client in the admin console and click on Credentials
. Then click on Generate registration access token
.
5.2. Keycloak Representations
The default
client registration provider can be used to create, retrieve, update and delete a client.
It uses Keycloak Client Representation format which provides support for configuring clients exactly as they can be configured through the admin
console, including for example configuring protocol mappers.
To create a client create a Client Representation (JSON) then perform an HTTP POST request to /auth/realms/<realm>/clients-registrations/default
.
It will return a Client Representation that also includes the registration access token.
You should save the registration access token somewhere if you want to retrieve the config, update or delete the client later.
To retrieve the Client Representation perform an HTTP GET request to /auth/realms/<realm>/clients-registrations/default/<client id>
.
It will also return a new registration access token.
To update the Client Representation perform an HTTP PUT request with the updated Client Representation to:
/auth/realms/<realm>/clients-registrations/default/<client id>
.
It will also return a new registration access token.
To delete the Client Representation perform an HTTP DELETE request to:
/auth/realms/<realm>/clients-registrations/default/<client id>
5.3. Keycloak Adapter Configuration
The installation
client registration provider can be used to retrieve the adapter configuration for a client.
In addition to token authentication you can also authenticate with client credentials using HTTP basic authentication.
To do this include the following header in the request:
Authorization: basic BASE64(client-id + ':' + client-secret)
To retrieve the Adapter Configuration then perform an HTTP GET request to /auth/realms/<realm>/clients-registrations/install/<client id>
.
No authentication is required for public clients.
This means that for the JavaScript adapter you can load the client configuration directly from Keycloak using the above URL.
5.4. OpenID Connect Dynamic Client Registration
The endpoint to use these specifications to register clients in Keycloak is /auth/realms/<realm>/clients-registrations/openid-connect[/<client id>]
.
This endpoint can also be found in the OpenID Connect Discovery endpoint for the realm, /auth/realms/<realm>/.well-known/openid-configuration
.
5.5. SAML Entity Descriptors
The SAML Entity Descriptor endpoint only supports using SAML v2 Entity Descriptors to create clients.
It doesn’t support retrieving, updating or deleting clients.
For those operations the Keycloak representation endpoints should be used.
When creating a client a Keycloak Client Representation is returned with details about the created client, including a registration access token.
To create a client perform an HTTP POST request with the SAML Entity Descriptor to /auth/realms/<realm>/clients-registrations/saml2-entity-descriptor
.
5.6. Example using CURL
The following example creates a client with the clientId myclient
using CURL. You need to replace eyJhbGciOiJSUz…
with a proper initial access token or
bearer token.
curl -X POST
-d '{ "clientId": "myclient" }'
-H "Content-Type:application/json"
-H "Authorization: bearer eyJhbGciOiJSUz..."
http://localhost:8080/auth/realms/master/clients-registrations/default
5.7. Example using Java Client Registration API
The Client Registration Java API makes it easy to use the Client Registration Service using Java.
To use include the dependency org.keycloak:keycloak-client-registration-api:>VERSION<
from Maven.
For full instructions on using the Client Registration refer to the JavaDocs.
Below is an example of creating a client. You need to replace eyJhbGciOiJSUz…
with a proper initial access token or bearer token.
String token = "eyJhbGciOiJSUz...";
ClientRepresentation client = new ClientRepresentation();
client.setClientId(CLIENT_ID);
ClientRegistration reg = ClientRegistration.create()
.url("http://localhost:8080/auth", "myrealm")
.build();
reg.auth(Auth.token(token));
client = reg.create(client);
String registrationAccessToken = client.getRegistrationAccessToken();
5.8. Client Registration Policies
Keycloak currently supports 2 ways how can be new clients registered through Client Registration Service.
-
Authenticated requests — Request to register new client must contain either
Initial Access Token
orBearer Token
as mentioned above. -
Anonymous requests — Request to register new client doesn’t need to contain any token at all
Anonymous client registration requests are very interesting and powerful feature, however you usually don’t want that anyone is able to register new
client without any limitations. Hence we have Client Registration Policy SPI
, which provide a way to limit who can register new clients and under which conditions.
In Keycloak admin console, you can click to Client Registration
tab and then Client Registration Policies
sub-tab. Here you will see what policies
are configured by default for anonymous requests and what policies are configured for authenticated requests.
The anonymous requests (requests without any token) are allowed just for creating (registration) of new clients. So when you register new client through anonymous request, the response will contain Registration Access Token, which must be used for Read, Update or Delete request of particular client. However using this Registration Access Token from anonymous registration will be then subject to Anonymous Policy too! This means that for example request for update client also needs to come from Trusted Host if you have Trusted Hosts policy. Also for example it won’t be allowed to disable Consent Required when updating client andwhen Consent Required policy is present etc.
|
Currently we have these policy implementations:
-
Trusted Hosts Policy — You can configure list of trusted hosts and trusted domains. Request to Client Registration Service can be sent just from those hosts or domains.
Request sent from some untrusted IP will be rejected. URLs of newly registered client must also use just those trusted hosts or domains. For example it won’t be allowed
to setRedirect URI
of client pointing to some untrusted host. By default, there is not any whitelisted host, so anonymous client registration is de-facto disabled. -
Consent Required Policy — Newly registered clients will have
Consent Allowed
switch enabled. So after successful authentication, user will always
see consent screen when he needs to approve permissions (client scopes). It means that client won’t have access to any personal
info or permission of user unless user approves it. -
Protocol Mappers Policy — Allows to configure list of whitelisted protocol mapper implementations. New client can’t be registered
or updated if it contains some non-whitelisted protocol mapper. Note that this policy is used for authenticated requests as well, so
even for authenticated request there are some limitations which protocol mappers can be used. -
Client Scope Policy — Allow to whitelist
Client Scopes
, which can be used with newly registered or updated clients.
There are no whitelisted scopes by default; only the client scopes, which are defined asRealm Default Client Scopes
are whitelisted by default. -
Full Scope Policy — Newly registered clients will have
Full Scope Allowed
switch disabled. This means they won’t have any scoped
realm roles or client roles of other clients. -
Max Clients Policy — Rejects registration if current number of clients in the realm is same or bigger than specified limit. It’s 200 by default for anonymous registrations.
-
Client Disabled Policy — Newly registered client will be disabled. This means that admin needs to manually approve and enable all newly registered clients.
This policy is not used by default even for anonymous registration.
6. Client Registration CLI
The Client Registration CLI is a command-line interface (CLI) tool for application developers to configure new clients in a self-service manner when integrating with Keycloak. It is specifically designed to interact with Keycloak Client Registration REST endpoints.
It is necessary to create or obtain a client configuration for any application to be able to use Keycloak. You usually configure a new client for each new application hosted on a unique host name. When an application interacts with Keycloak, the application identifies itself with a client ID so Keycloak can provide a login page, single sign-on (SSO) session management, and other services.
You can configure application clients from a command line with the Client Registration CLI, and you can use it in shell scripts.
To allow a particular user to use Client Registration CLI
the Keycloak administrator typically uses the Admin Console to configure a new user with proper roles or to configure a new client and client secret to grant access to the Client Registration REST API.
6.1. Configuring a new regular user for use with Client Registration CLI
-
Log in to the Admin Console (for example, http://localhost:8080/auth/admin) as
admin
. -
Select a realm to administer.
-
If you want to use an existing user, select that user to edit; otherwise, create a new user.
-
Select Role Mappings > Client Roles > realm-management. If you are in the master realm, select NAME-realm, where
NAME
is the name of the target realm. You can grant access to any other realm to users in the master realm. -
Select Available Roles > manage-client to grant a full set of client management permissions. Another option is to choose view-clients for read-only or create-client to create new clients.
These permissions grant the user the capability to perform operations without the use of Initial Access Token or Registration Access Token.
It is possible to not assign any realm-management
roles to a user. In that case, a user can still log in with the Client Registration CLI but cannot use it without an Initial Access Token. Trying to perform any operations without a token results in a 403 Forbidden error.
The Administrator can issue Initial Access Tokens from the Admin Console through the Realm Settings > Client Registration > Initial Access Token menu.
6.2. Configuring a client for use with the Client Registration CLI
By default, the server recognizes the Client Registration CLI as the admin-cli
client, which is configured automatically for every new realm. No additional client configuration is necessary when logging in with a user name.
-
Create a new client (for example,
reg-cli
) if you want to use a separate client configuration for the Client Registration CLI. -
Toggle the Standard Flow Enabled setting it to Off.
-
Strengthen the security by configuring the client
Access Type
asConfidential
and selecting Credentials > ClientId and Secret.You can configure either
Client Id and Secret
orSigned JWT
under the Credentials tab . -
Enable service accounts if you want to use a service account associated with the client by selecting a client to edit in the Clients section of the
Admin Console
.-
Under Settings, change the Access Type to Confidential, toggle the Service Accounts Enabled setting to On, and click Save.
-
Click Service Account Roles and select desired roles to configure the access for the service account. For the details on what roles to select, see Configuring a new regular user for use with Client Registration CLI.
-
-
Toggle the Direct Access Grants Enabled setting it to On if you want to use a regular user account instead of a service account.
-
If the client is configured as
Confidential
, provide the configured secret when runningkcreg config credentials
by using the--secret
option. -
Specify which
clientId
to use (for example,--client reg-cli
) when runningkcreg config credentials
. -
With the service account enabled, you can omit specifying the user when running
kcreg config credentials
and only provide the client secret or keystore information.
6.3. Installing the Client Registration CLI
The Client Registration CLI is packaged inside the Keycloak Server distribution. You can find execution scripts inside the bin
directory. The Linux script is called kcreg.sh
, and the Windows script is called kcreg.bat
.
Add the Keycloak server directory to your PATH
when setting up the client for use from any location on the file system.
For example, on:
-
Linux:
$ export PATH=$PATH:$KEYCLOAK_HOME/bin $ kcreg.sh
-
Windows:
c:> set PATH=%PATH%;%KEYCLOAK_HOME%bin c:> kcreg
KEYCLOAK_HOME
refers to a directory where the Keycloak Server distribution was unpacked.
6.4. Using the Client Registration CLI
-
Start an authenticated session by logging in with your credentials.
-
Run commands on the
Client Registration REST
endpoint.For example, on:
-
Linux:
$ kcreg.sh config credentials --server http://localhost:8080/auth --realm demo --user user --client reg-cli $ kcreg.sh create -s clientId=my_client -s 'redirectUris=["http://localhost:8980/myapp/*"]' $ kcreg.sh get my_client
-
Windows:
c:> kcreg config credentials --server http://localhost:8080/auth --realm demo --user user --client reg-cli c:> kcreg create -s clientId=my_client -s "redirectUris=["http://localhost:8980/myapp/*"]" c:> kcreg get my_client
In a production environment, Keycloak has to be accessed with
https:
to avoid exposing tokens to network sniffers.
-
-
If a server’s certificate is not issued by one of the trusted certificate authorities (CAs) that are included in Java’s default certificate truststore, prepare a
truststore.jks
file and instruct the Client Registration CLI to use it.For example, on:
-
Linux:
$ kcreg.sh config truststore --trustpass $PASSWORD ~/.keycloak/truststore.jks
-
Windows:
c:> kcreg config truststore --trustpass %PASSWORD% %HOMEPATH%.keycloaktruststore.jks
-
6.4.1. Logging in
-
Specify a server endpoint URL and a realm when you log in with the Client Registration CLI.
-
Specify a user name or a client id, which results in a special service account being used. When using a user name, you must use a password for the specified user. When using a client ID, you use a client secret or a
Signed JWT
instead of a password.
Regardless of the login method, the account that logs in needs proper permissions to be able to perform client registration operations. Keep in mind that any account in a non-master realm can only have permissions to manage clients within the same realm. If you need to manage different realms, you can either configure multiple users in different realms, or you can create a single user in the master
realm and add roles for managing clients in different realms.
You cannot configure users with the Client Registration CLI. Use the Admin Console web interface or the Admin Client CLI to configure users. See Server Administration Guide for more details.
When kcreg
successfully logs in, it receives authorization tokens and saves them in a private configuration file so the tokens can be used for subsequent invocations. See Working with alternative configurations for more information on configuration files.
See the built-in help for more information on using the Client Registration CLI.
For example, on:
-
Linux:
-
Windows:
See kcreg config credentials --help
for more information about starting an authenticated session.
6.4.2. Working with alternative configurations
By default, the Client Registration CLI automatically maintains a configuration file at a default location, ./.keycloak/kcreg.config
, under the user’s home directory. You can use the --config
option to point to a different file or location to mantain multiple authenticated sessions in parallel. It is the safest way to perform operations tied to a single configuration file from a single thread.
Do not make the configuration file visible to other users on the system. The configuration file contains access tokens and secrets that should be kept private. |
You might want to avoid storing secrets inside a configuration file by using the --no-config
option with all of your commands, even though it is less convenient and requires more token requests to do so. Specify all authentication information with each kcreg
invocation.
6.4.3. Initial Access and Registration Access Tokens
Developers who do not have an account configured at the Keycloak server they want to use can use the Client Registration CLI. This is possible only when the realm administrator issues a developer an Initial Access Token. It is up to the realm administrator to decide how and when to issue and distribute these tokens. The realm administrator can limit the maximum age of the Initial Access Token and the total number of clients that can be created with it.
Once a developer has an Initial Access Token, the developer can use it to create new clients without authenticating with kcreg config credentials
. The Initial Access Token can be stored in the configuration file or specified as part of the kcreg create
command.
For example, on:
-
Linux:
$ kcreg.sh config initial-token $TOKEN $ kcreg.sh create -s clientId=myclient
or
$ kcreg.sh create -s clientId=myclient -t $TOKEN
-
Windows:
c:> kcreg config initial-token %TOKEN% c:> kcreg create -s clientId=myclient
or
c:> kcreg create -s clientId=myclient -t %TOKEN%
When using an Initial Access Token, the server response includes a newly issued Registration Access Token. Any subsequent operation for that client needs to be performed by authenticating with that token, which is only valid for that client.
The Client Registration CLI automatically uses its private configuration file to save and use this token with its associated client. As long as the same configuration file is used for all client operations, the developer does not need to authenticate to read, update, or delete a client that was created this way.
See Client Registration for more information about Initial Access and Registration Access Tokens.
Run the kcreg config initial-token --help
and kcreg config registration-token --help
commands for more information on how to configure tokens with the Client Registration CLI.
6.4.4. Creating a client configuration
The first task after authenticating with credentials or configuring an Initial Access Token is usually to create a new client. Often you might want to use a prepared JSON file as a template and set or override some of the attributes.
The following example shows how to read a JSON file, override any client id it may contain, set any other attributes, and print the configuration to a standard output after successful creation.
-
Linux:
$ kcreg.sh create -f client-template.json -s clientId=myclient -s baseUrl=/myclient -s 'redirectUris=["/myclient/*"]' -o
-
Windows:
C:> kcreg create -f client-template.json -s clientId=myclient -s baseUrl=/myclient -s "redirectUris=["/myclient/*"]" -o
Run the kcreg create --help
for more information about the kcreg create
command.
You can use kcreg attrs
to list available attributes. Keep in mind that many configuration attributes are not checked for validity or consistency. It is up to you to specify proper values. Remember that you should not have any id fields in your
template and should not specify them as arguments to the kcreg create
command.
6.4.5. Retrieving a client configuration
You can retrieve an existing client by using the kcreg get
command.
For example, on:
-
Linux:
-
Windows:
You can also retrieve the client configuration as an adapter configuration file, which you can package with your web application.
For example, on:
-
Linux:
$ kcreg.sh get myclient -e install > keycloak.json
-
Windows:
C:> kcreg get myclient -e install > keycloak.json
Run the kcreg get --help
command for more information about the kcreg get
command.
6.4.6. Modifying a client configuration
There are two methods for updating a client configuration.
One method is to submit a complete new state to the server after getting the current configuration, saving it to a file, editing it, and posting it back to the server.
For example, on:
-
Linux:
$ kcreg.sh get myclient > myclient.json $ vi myclient.json $ kcreg.sh update myclient -f myclient.json
-
Windows:
C:> kcreg get myclient > myclient.json C:> notepad myclient.json C:> kcreg update myclient -f myclient.json
The second method fetches the current client, sets or deletes fields on it, and posts it back in one step.
For example, on:
-
Linux:
$ kcreg.sh update myclient -s enabled=false -d redirectUris
-
Windows:
C:> kcreg update myclient -s enabled=false -d redirectUris
You can also use a file that contains only changes to be applied so you do not have to specify too many values as arguments. In this case, specify --merge
to tell the Client Registration CLI that rather than treating the JSON file as a full, new configuration, it should treat it as a set of attributes to be applied over the existing configuration.
For example, on:
-
Linux:
$ kcreg.sh update myclient --merge -d redirectUris -f mychanges.json
-
Windows:
C:> kcreg update myclient --merge -d redirectUris -f mychanges.json
Run the kcreg update --help
command for more information about the kcreg update
command.
6.4.7. Deleting a client configuration
Use the following example to delete a client.
-
Linux:
$ kcreg.sh delete myclient
-
Windows:
C:> kcreg delete myclient
Run the kcreg delete --help
command for more information about the kcreg delete
command.
6.4.8. Refreshing invalid Registration Access Tokens
When performing a create, read, update, and delete (CRUD) operation using the --no-config
mode, the Client Registration CLI cannot handle Registration Access Tokens for you. In that case, it is possible to lose track of the most recently issued Registration Access Token for a client, which makes it impossible to perform any further CRUD operations on that client without authenticating with an account that has manage-clients permissions.
If you have permissions, you can issue a new Registration Access Token for the client and have it printed to a standard output or saved to a configuration file of your choice. Otherwise, you have to ask the realm administrator to issue a new Registration Access Token for your client and send it to you. You can then pass it to any CRUD command via the --token
option. You can also use the kcreg config registration-token
command to save the new token in a configuration file and have the Client Registration CLI automatically handle it for you from that point on.
Run the kcreg update-token --help
command for more information about the kcreg update-token
command.
6.5. Troubleshooting
-
Q: When logging in, I get an error: Parameter client_assertion_type is missing [invalid_client].
A: This error means your client is configured with
Signed JWT
token credentials, which means you have to use the--keystore
parameter when logging in.
7. Token Exchange
Token Exchange is Technology Preview and is not fully supported. This feature is disabled by default. To enable start the server with |
In order to use token exchange you should also enable the |
In Keycloak, token exchange is the process of using a set of credentials or token to obtain an entirely different token.
A client may want to invoke on a less trusted application so it may want to downgrade the current token it has.
A client may want to exchange a Keycloak token for a token stored for a linked social provider account.
You may want to trust external tokens minted by other Keycloak realms or foreign IDPs. A client may have a need
to impersonate a user. Here’s a short summary of the current capabilities of Keycloak around token exchange.
-
A client can exchange an existing Keycloak token created for a specific client for a new token targeted to a different client
-
A client can exchange an existing Keycloak token for an external token, i.e. a linked Facebook account
-
A client can exchange an external token for a Keycloak token.
-
A client can impersonate a user
Token exchange in Keycloak is a very loose implementation of the OAuth Token Exchange specification at the IETF.
We have extended it a little, ignored some of it, and loosely interpreted other parts of the specification. It is
a simple grant type invocation on a realm’s OpenID Connect token endpoint.
/auth/realms/{realm}/protocol/openid-connect/token
It accepts form parameters (application/x-www-form-urlencoded
) as input and the output depends on the type of token you requested an exchange for.
Token exchange is a client endpoint so requests must provide authentication information for the calling client.
Public clients specify their client identifier as a form parameter. Confidential clients can also use form parameters
to pass their client id and secret, Basic Auth, or however your admin has configured the client authentication flow in your
realm. Here’s a list of form parameters
- client_id
-
REQUIRED MAYBE. This parameter is required for clients using form parameters for authentication. If you are using
Basic Auth, a client JWT token, or client cert authentication, then do not specify this parameter. - client_secret
-
REQUIRED MAYBE. This parameter is required for clients using form parameters for authentication and using a client secret as a credential.
Do not specify this parameter if client invocations in your realm are authenticated by a different means. - grant_type
-
REQUIRED. The value of the parameter must be
urn:ietf:params:oauth:grant-type:token-exchange
. - subject_token
-
OPTIONAL. A security token that represents the identity of the party on behalf of whom the request is being made. It is required if you are exchanging an existing token for a new one.
- subject_issuer
-
OPTIONAL. Identifies the issuer of the
subject_token
. It can be left blank if the token comes from the current realm or if the issuer
can be determined from thesubject_token_type
. Otherwise it is required to be specified. Valid values are the alias of anIdentity Provider
configured for your realm. Or an issuer claim identifier
configured by a specificIdentity Provider
. - subject_token_type
-
OPTIONAL. This parameter is the type of the token passed with the
subject_token
parameter. This defaults
tourn:ietf:params:oauth:token-type:access_token
if thesubject_token
comes from the realm and is an access token.
If it is an external token, this parameter may or may not have to be specified depending on the requirements of the
subject_issuer
. - requested_token_type
-
OPTIONAL. This parameter represents the type of token the client wants to exchange for. Currently only oauth
and OpenID Connect token types are supported. The default value for this depends on whether the
isurn:ietf:params:oauth:token-type:refresh_token
in which case you will be returned both an access token and refresh
token within the response. Other appropriate values areurn:ietf:params:oauth:token-type:access_token
andurn:ietf:params:oauth:token-type:id_token
- audience
-
OPTIONAL. This parameter specifies the target client you want the new token minted for.
- requested_issuer
-
OPTIONAL. This parameter specifies that the client wants a token minted by an external provider. It must
be the alias of anIdentity Provider
configured within the realm. - requested_subject
-
OPTIONAL. This specifies a username or user id if your client wants to impersonate a different user.
- scope
-
NOT IMPLEMENTED. This parameter represents the target set of OAuth and OpenID Connect scopes the client
is requesting. It is not implemented at this time but will be once Keycloak has better support for
scopes in general.
We currently only support OpenID Connect and OAuth exchanges. Support for SAML based clients and identity providers may be added in the future depending on user demand. |
A successful response from an exchange invocation will return the HTTP 200 response code with a content type that
depends on the requested-token-type
and requested_issuer
the client asks for. OAuth requested token types will return
a JSON document as described in the OAuth Token Exchange specification.
{
"access_token" : ".....",
"refresh_token" : ".....",
"expires_in" : "...."
}
Clients requesting a refresh token will get back both an access and refresh token in the response. Clients requesting only
access token type will only get an access token in the response. Expiration information may or may not be included for
clients requesting an external issuer through the requested_issuer
paramater.
Error responses generally fall under the 400 HTTP response code category, but other error status codes may be returned
depending on the severity of the error. Error responses may include content depending on the requested_issuer
.
OAuth based exchanges may return a JSON document as follows:
{
"error" : "...."
"error_description" : "...."
}
Additional error claims may be returned depending on the exchange type. For example, OAuth Identity Providers may include
an additional account-link-url
claim if the user does not have a link to an identity provider. This link can be used
for a client initiated link request.
Token exchange setup requires knowledge of fine grain admin permissions (See the Server Administration Guide for more information). You will need to grant clients permission to exchange. This is discussed more later in this chapter. |
The rest of this chapter discusses the setup requirements and provides examples for different exchange scenarios.
For simplicity’s sake, let’s call a token minted by the current realm as an internal token and a token minted by
an external realm or identity provider as an external token.
7.1. Internal Token to Internal Token Exchange
With an internal token to token exchange you have an existing token minted to a specific client and you want to exchange
this token for a new one minted for a different target client. Why would you want to do this? This generally happens
when a client has a token minted for itself, and needs to make additional requests to other applications that require different
claims and permissions within the access token. Other reasons this type of exchange might be required is if you
need to perform a «permission downgrade» where your app needs to invoke on a less trusted app and you don’t want
to propagate your current access token.
7.1.1. Granting Permission for the Exchange
Clients that want to exchange tokens for a different client need to be authorized in the admin console to do so.
You’ll need to define a token-exchange
fine grain permission in the target client you want permission to exchange to.
Target Client Permission
Toggle the Permissions Enabled
switch to ON.
Target Client Permission
You should see a token-exchange
link on the page. Click that to start defining the permission. It will bring you
to this page.
Target Client Exchange Permission Setup
You’ll have to define a policy for this permission. Click the Authorization
link, go to the Policies
tab and create
a Client
Policy.
Client Policy Creation
Here you enter in the starting client, that is the authenticated client that is requesting a token exchange. After you
create this policy, go back to the target client’s token-exchange
permission and add the client policy you just
defined.
Apply Client Policy
Your client now has permission to invoke. If you do not do this correctly, you will get a 403 Forbidden response if you
try to make an exchange.
7.1.2. Making the Request
When your client is exchanging an existing token for a token targeting another client, you must use the audience
parameter.
This parameter must be the client identifier for the target client that you configured in the admin console.
curl -X POST
-d "client_id=starting-client"
-d "client_secret=geheim"
--data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange"
-d "subject_token=...."
--data-urlencode "requested_token_type=urn:ietf:params:oauth:token-type:refresh_token"
-d "audience=target-client"
http://localhost:8080/auth/realms/myrealm/protocol/openid-connect/token
The subject_token
parameter must be an access token for the target realm. If your requested_token_type
parameter
is a refresh token type, then the response will contain both an access token, refresh token, and expiration. Here’s
an example JSON response you get back from this call.
{
"access_token" : "....",
"refresh_token" : "....",
"expires_in" : 3600
}
7.2. Internal Token to External Token Exchange
You can exchange a realm token for an externl token minted by an external identity provider. This external identity provider
must be configured within the Identity Provider
section of the admin console. Currently only OAuth/OpenID Connect based external
identity providers are supported, this includes all social providers. Keycloak does not perform a backchannel exchange to the external provider. So if the account
is not linked, you will not be able to get the external token. To be able to obtain an external token one of
these conditions must be met:
-
The user must have logged in with the external identity provider at least once
-
The user must have linked with the external identity provider through the User Account Service
-
The user account was linked through the external identity provider using Client Initiated Account Linking API.
Finally, the external identity provider must have been configured to store tokens, or, one of the above actions must
have been performed with the same user session as the internal token you are exchanging.
If the account is not linked, the exchange response will contain a link you can use to establish it. This is
discussed more in the Making the Request section.
7.2.1. Granting Permission for the Exchange
Internal to external token exchange requests will be denied with a 403, Forbidden response until you grant
permission for the calling client to exchange tokens with the external identity provider. To grant permission
to the client you must go to the identity provider’s configuration page to the Permissions
tab.
Identity Provider Permission
Toggle the Permissions Enabled
switch to true.
Identity Provider Permission
You should see a token-exchange
link on the page. Click that to start defining the permission. It will bring you
to this page.
Identity Provider Exchange Permission Setup
You’ll have to define a policy for this permission. Click the Authorization
link, go to the Policies
tab and create
a Client
Policy.
Client Policy Creation
Here you enter in the starting client, that is the authenticated client that is requesting a token exchange. After you
create this policy, go back to the identity providers’s token-exchange
permission and add the client policy you just
defined.
Apply Client Policy
Your client now has permission to invoke. If you do not do this correctly, you will get a 403 Forbidden response if you
try to make an exchange.
7.2.2. Making the Request
When your client is exchanging an existing internal token to an external one, you must provide the
requested_issuer
parameter. The parameter must be the alias of a configured identity provider.
curl -X POST
-d "client_id=starting-client"
-d "client_secret=geheim"
--data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange"
-d "subject_token=...."
--data-urlencode "requested_token_type=urn:ietf:params:oauth:token-type:access_token"
-d "requested_issuer=google"
http://localhost:8080/auth/realms/myrealm/protocol/openid-connect/token
The subject_token
parameter must be an access token for the target realm. The requested_token_type
parameter
must be urn:ietf:params:oauth:token-type:access_token
or left blank. No other requested token type is supported
at this time. Here’s
an example successful JSON response you get back from this call.
{
"access_token" : "....",
"expires_in" : 3600
"account-link-url" : "https://...."
}
If the external identity provider is not linked for whatever reason, you will get an HTTP 400 response code with
this JSON document:
{
"error" : "....",
"error_description" : "..."
"account-link-url" : "https://...."
}
The error
claim will be either token_expired
or not_linked
. The account-link-url
claim is provided
so that the client can perform Client Initiated Account Linking. Most (all?)
providers are requiring linking through browser OAuth protocol. With the account-link-url
just add a redirect_uri
query parameter to it and you can forward browsers to perform the link.
7.3. External Token to Internal Token Exchange
You can trust and exchange external tokens minted by external identity providers for internal tokens. This can be
used to bridge between realms or just to trust tokens from your social provider. It works similarly to an identity provider
browser login in that a new user is imported into your realm if it doesn’t exist.
The current limitation on external token exchanges is that if the external token maps to an existing user an exchange will not be allowed unless the existing user already has an account link to the external identity provider. |
When the exchange is complete, a user session will be created within the realm, and you will receive an access
and or refresh token depending on the requested_token_type
parameter value. You should note that this new
user session will remain active until it times out or until you call the logout endpoint of the realm passing this
new access token.
These types of changes required a configured identity provider in the admin console.
SAML identity providers are not supported at this time. Twitter tokens cannot be exchanged either. |
7.3.1. Granting Permission for the Exchange
Before external token exchanges can be done, you must grant permission for the calling client to make the exchange. This
permission is granted in the same manner as internal to external permission is granted.
If you also provide an audience
parameter whose value points to a different client other than the calling one, you
must also grant the calling client permission to exchange to the target client specific in the audience
parameter. How
to do this is discussed earlier in this section.
7.3.2. Making the Request
The subject_token_type
must either be urn:ietf:params:oauth:token-type:access_token
or urn:ietf:params:oauth:token-type:jwt
.
If the type is urn:ietf:params:oauth:token-type:access_token
you must specify the subject_issuer
parameter and it must be the
alias of the configured identity provider. If the type is urn:ietf:params:oauth:token-type:jwt
, the provider will be matched via
the issuer
claim within the JWT which must be the alias of the provider, or a registered issuer within the providers configuration.
For validation, if the token is an access token, the provider’s user info service will be invoked to validate the token. A successful call
will mean that the access token is valid. If the subject token is a JWT and if the provider has signature validation enabled, that will be attempted,
otherwise, it will default to also invoking on the user info service to validate the token.
By default, the internal token minted will use the calling client to determine what’s in the token using the protocol
mappers defined for the calling client. Alternatively, you can specify a different target client using the audience
parameter.
curl -X POST
-d "client_id=starting-client"
-d "client_secret=geheim"
--data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange"
-d "subject_token=...."
-d "subject_issuer=myOidcProvider"
--data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:access_token"
-d "audience=target-client"
http://localhost:8080/auth/realms/myrealm/protocol/openid-connect/token
If your requested_token_type
parameter
is a refresh token type, then the response will contain both an access token, refresh token, and expiration. Here’s
an example JSON response you get back from this call.
{
"access_token" : "....",
"refresh_token" : "....",
"expires_in" : 3600
}
7.4. Impersonation
For internal and external token exchanges, the client can request on behalf of a user to impersonate a different user.
For example, you may have an admin application that needs to impersonate a user so that a support engineer can debug
a problem.
7.4.1. Granting Permission for the Exchange
The user that the subject token represents must have permission to impersonate other users. See the
Server Administration Guide on how to enable this permission. It can be done through a role or through
fine grain admin permissions.
7.4.2. Making the Request
Make the request as described in other chapters except additionally specify the requested_subject
parameter. The
value of this parameter must be a username or user id.
curl -X POST
-d "client_id=starting-client"
-d "client_secret=geheim"
--data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange"
-d "subject_token=...."
--data-urlencode "requested_token_type=urn:ietf:params:oauth:token-type:access_token"
-d "audience=target-client"
-d "requested_subject=wburke"
http://localhost:8080/auth/realms/myrealm/protocol/openid-connect/token
7.5. Direct Naked Impersonation
You can make an internal token exchange request without providing a subject_token
. This is called a direct
naked impersonation because it places a lot of trust in a client as that client can impersonate any user in the realm.
You might need this to bridge for applications where it is impossible to obtain a subject token to exchange. For example,
you may be integrating a legacy application that performs login directly with LDAP. In that case, the legacy app
is able to authenticate users itself, but not able to obtain a token.
It is very risky to enable direct naked impersonation for a client. If the client’s credentials are ever stolen, that client can impersonate any user in the system. |
7.5.1. Granting Permission for the Exchange
If the audience
parameter is provided, then the calling client must have permission to exchange to the client. How
to set this up is discussed earlier in this chapter.
Additionally, the calling client must be granted permission to impersonate users. In the admin console, go to the
Users
screen and click on the Permissions
tab.
Users Permission
Toggle the Permissions Enabled
switch to true.
Identity Provider Permission
You should see a impersonation
link on the page. Click that to start defining the permission. It will bring you
to this page.
Users Impersonation Permission Setup
You’ll have to define a policy for this permission. Click the Authorization
link, go to the Policies
tab and create
a Client
Policy.
Client Policy Creation
Here you enter in the starting client, that is the authenticated client that is requesting a token exchange. After you
create this policy, go back to the users’ impersonation
permission and add the client policy you just
defined.
Apply Client Policy
Your client now has permission to impersonate users. If you do not do this correctly, you will get a 403 Forbidden response if you
try to make this type of exchange.
Public clients are not allowed to do direct naked impersonations. |
7.5.2. Making the Request
To make the request, simply specify the requested_subject
parameter. This must be the username or user id of
a valid user. You can also specify an audience
parameter if you wish.
curl -X POST
-d "client_id=starting-client"
-d "client_secret=geheim"
--data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange"
-d "requested_subject=wburke"
http://localhost:8080/auth/realms/myrealm/protocol/openid-connect/token
7.6. Expand Permission Model With Service Accounts
When granting clients permission to exchange, you don’t necessarily have to manually enable those permissions for each and every client.
If the client has a service account associated with it, you can use a role to group permissions together and assign exchange permissions
by assigning a role to the client’s service account. For example, you might define a naked-exchange
role and any service account that has that
role can do a naked exchange.
7.7. Exchange Vulnerabilities
When you start allowing token exchanges, there’s various things you have to both be aware of and careful of.
The first is public clients. Public clients do not have or require a client credential in order to perform an exchange. Anybody that has a valid
token will be able to impersonate the public client and perform the exchanges that public client is allowed to perform. If there
are any untrustworthy clients that are managed by your realm, public clients may open up vulnerabilities in your permission models.
This is why direct naked exchanges do not allow public clients and will abort with an error if the calling client is public.
It is possible to exchange social tokens provided by Facebook, Google, etc. for a realm token. Be careful and vigilante on what
the exchange token is allowed to do as its not hard to create fake accounts on these social websites. Use default roles, groups, and identity provider mappers to control what attributes and roles
are assigned to the external social user.
Direct naked exchanges are quite dangerous. You are putting a lot of trust in the calling client that it will never leak out
its client credentials. If those credentials are leaked, then the thief can impersonate anybody in your system. This is in direct
contrast to confidential clients that have existing tokens. You have two factors of authentication, the access token and the client
credentials, and you’re only dealing with one user. So use direct naked exchanges sparingly.
This article describes how you can use Keycloak behind an API-gateway that considers CORS requests, which by default does not work. It presents a tutorial for a work-around that can be used until a proper fix for the root-cause of the problem is provided by Keycloak.
The Problem
The default setup will cause an HTTP 403 Forbidden response from the API-gateway during the authenticate-step on the Keycloak login page because the browser sends the HTTP request-header ‘origin: null‘, which is identified by the API-gateway as a CORS-request, and denied because ‘null‘ is not an allowed origin.
The root-cause for this behavior is that Keycloak always sends the HTTP response-header ‘Referrer-Policy: no-referrer‘. This instructs the browser to omit the Referer HTTP request-header, and send the HTTP request-header ‘origin: null‘ for subsequent requests to Keycloak.
Our solution is to rewrite the HTTP response-header from Keycloak in the API-gateway, so that instead of ‘Referrer-Policy: no-referrer‘ the browser will receive ‘Referrer-Policy: same-origin‘.
Background
An API-gateway, such as Spring Cloud Gateway, is typically used in a microservice architecture to expose multiple services at a single endpoint.
It is not uncommon to expose an IAM-solution, such as Keycloak, via an API-gateway.
Services may require CORS support for some endpoints, which is typically managed at the API-gateway level.
Keycloak
According to https://www.keycloak.org/about “Keycloak is an open-source Identity and Access Management solution aimed at modern applications and services. It makes it easy to secure applications and services with little to no code.”
Spring Cloud Gateway
According to https://spring.io/projects/spring-cloud-gateway the Spring Cloud Gateway “ provides a library for building an API Gateway on top of Spring WebFlux. Spring Cloud Gateway aims to provide a simple, yet effective way to route to APIs and provide cross-cutting concerns to them such as security, monitoring/metrics, and resiliency.”
CORS
According to https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS “Cross-Origin Resource Sharing (CORS) is an HTTP-header based mechanism that allows a server to indicate any origins (domain, scheme, or port) other than its own from which a browser should permit loading resources.”
How to Reproduce the Problem
In order to reproduce the problem, you need the following setup:
- Keycloak version 12 or higher (version 12 introduced the Referrer-Policy response header, although there is a bug report that states it also occurs with version 11.0.2)
- API-gateway such as Spring Cloud Gateway with CORS enabled
The following steps will then reproduce the problem:
- Navigate to the Keycloak login page with a browser (we used both Firefox and Chrome), e.g. http://localhost:8080/auth/admin/ for a local setup.
- Observe the HTTP response-header ‘Referrer-Policy: no-referrer‘ in the browser developer-tools network tab.
- Provide valid login credentials, and click the Sign In button.
- Observe the HTTP response 403 Forbidden and the empty page in the browser.
- Observe the HTTP request header ‘origin: null’.
If you cannot reproduce the problem, be sure that:
- The browser communicates with the API-gateway, and not with Keycloak directly.
- CORS is enabled in the API-gateway using non-wildcard ‘Access-Control-Allow-Origin‘, e.g. using configuration properties such as
spring.cloud.gateway.globalcors.cors-configurations.[/**].allowedOrigins="https://some.domain.org"
spring.cloud.gateway.globalcors.cors-configurations.[/**].allowedMethods=GET
The Solution
The problem can be fixed (or rather, worked-around) by rewriting the HTTP response-header from Keycloak in the API-gateway, e.g. so that instead of ‘Referrer-Policy: no-referrer‘ the browser receives ‘Referrer-Policy: same-origin‘.
Spring Cloud Gateway provides a convenient RewriteResponseHeaderGatewayFilterFactory for this, which we set up as follows:
@Bean
public RouteLocator theRoutes(RouteLocatorBuilder builder) {
return builder.routes()
.route("auth", r ->
r.path("/auth/**")
.filters(f -> f.rewriteResponseHeader("Referrer-Policy", "no-referrer", "same-origin"))
.uri("https://keycloak"))
.build();
}
Then you can repeat the steps to observe that the fix works
- Navigate to the Keycloak login page with a browser, e.g. http://localhost:8080/auth/admin/ for a local setup
- Observe the HTTP response-header ‘Referrer-Policy: same-origin‘ in the browser developer-tools network tab
- Provide valid login credentials, and click the Sign In button.
- Observe the HTTP response 302 Found and the target page in the browser.
- Observe the HTTP request header ‘origin: http://localhost:8080‘.
Questions and Considerations
Why Is the API-gateway Rejecting the HTTP Request?
The browser sends the HTTP request-header ‘origin: null‘ when the ‘Referrer-Policy‘ is ‘no-referrer‘.
Whenever the ‘origin‘ header is present in the HTTP request, the API-gateway considers it a CORS request. A CORS request causes the API-gateway to validate if the origin is in the list of allowed origins. For ‘null‘ this is typically not the case (as it’s not recommended), leading it to reject the request with HTTP 403 Forbidden.
Why Don’t You Just Allow null as CORS Origin?
Allowing ‘null‘ as ‘Access-Control-Allow-Origin‘ is not recommended, as it introduces multiple security problems. See https://w3c.github.io/webappsec-cors-for-developers/#avoid-returning-access-control-allow-origin-null for more details.
Why Don’t You Just Allow All Origins / Methods for CORS?
While allowing all origins/methods for CORS would prevent the problem, it would also introduce significant security issues. CORS settings should always be as restrictive as possible, especially when sensitive data (such as credentials) is involved. See https://stackoverflow.com/a/56457665 for details.
Shouldn’t Keycloak Fix This Issue?
We would recommend Keycloak to make the ‘Referrer-Policy‘ value configurable, just as they allow for certain other headers.
The following Keycloak bug-report was created (not by us) in October 2020 for this issue, suggesting exactly this: https://issues.redhat.com/browse/KEYCLOAK-16032
Somehow the Keycloak devs got confused in the bug thread and closed it as ‘explained’ by stating that ‘null‘ is a valid value for ‘origin‘ (which is not the issue), rather than actually fixing the problem (e.g. by making the ‘Referrer-Policy‘ customizable, similar to other HTTP response header values).
Doesn’t This Work-Around Reduce Overall Security?
It is true that ‘no-referrer‘ is a stricter ‘Referrer-Policy‘ compared to e.g. ‘same-origin‘, as it causes the browser to send fewer details to the API-gateway in the ‘Referer‘ and ‘origin‘ headers.
However, in combination with a CORS-enabled API-gateway, using ‘no-referrer‘ totally breaks Keycloak from a system-level perspective, rendering it completely useless. So using this setting provides the same level of security and functionality as shutting down Keycloak.
In order to make this work with a CORS-enabled API-gateway, the browser must send some more details, specifically a proper value for the ‘origin‘ header for requests with the same origin. This can be achieved by using a different ‘Referrer-Policy‘, such as ‘same-origin‘.
For us this is an acceptable (very limited) reduction in security to at least have the expected functionality; in addition, these details only affect the same origin, which is fully under our control.
Can I Use Other Referrer-Policy Values?
You can choose a referrer-policy that works for your setup, there is no requirement to use ‘same-origin‘. For example, the typical browser-default ‘strict-origin-when-cross-origin‘ works fine as well.
Further Details
- Referrer-Policy response header
- Keycloak always sending ‘Referrer-Policy: no-referrer‘ Part 1 + Part 2
- Bug report for Keycloak that was not fixed but only explained
- Why origin ‘null‘ should not be an allowed origin for CORS
- Spring Cloud Gateway RewriteResponseHeaderGatewayFilterFactory
- Spring CorsUtils to determine if an HTTP request is CORS or not
- General information on CORS
- Chromium devs confirming the no-referrer policy behavior
I am able to login to Keycloak using the keycloak-js
client, however, when making a fetch
request, I get the following error:
Access to fetch at 'https://xxxxxxxx.com/auth/realms/app_testing/protocol/openid-connect/token' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
The post request I am making is
var formData = new FormData() formData.append("client_id", 'vue_blog_gui'); formData.append("grant_type", "password"); formData.append("client_secret", "705669d0-xxxx-xxxx-xxxx-4f4e52e3196b"); formData.append("scope", "openid"); formData.append("username", "user@example.com") formData.append("password", "123") fetch( 'https://xxxxxxxx.com/auth/realms/app_testing/protocol/openid-connect/token', { method: 'POST', 'Content-Type': 'application/x-www-form-urlencoded', data: formData } )
The keycloak settings are
- Root URL:
http://localhost:8080
- Valid Redirect URIs:
http://localhost:8080
- Base URL:
/
- Admin URL: Empty
- Web Origins:
*
// but I have also triedhttp://localhost:8080
and +
My app is running on http://localhost:8080
Advertisement
Answer
I managed to solve the problem. It was the format of the data I was sending to Keycloak. I need to URLEncode the FormData adding it to the body of the Fetch request. Also, was using data
rather than body
in the fetch request.
Anyway, I solved it by putting all the data into PostMan, getting it working in there and then taking the Auto Code generation that Postman provides to come up with this.
var myHeaders = new Headers(); myHeaders.append('Content-Type', 'application/x-www-form-urlencoded'); var urlencoded = new URLSearchParams(); urlencoded.append('client_id', 'vue_blog_gui'); urlencoded.append('username', 'me@example.com'); urlencoded.append('password', 'password'); urlencoded.append('grant_type', 'password'); urlencoded.append('scope', 'openid'); urlencoded.append('client_secret', '705669d0-xxxx-xxxx-xxxx-4f4e52e3196b'); var requestOptions = { method: 'POST', headers: myHeaders, body: urlencoded, redirect: 'follow', }; fetch( 'https://keycloak.server.example.com/auth/realms/app_testing/protocol/openid-connect/token', requestOptions ) .then((response) => response.text()) .then((result) => console.log(result)) .catch((error) => console.log('error', error));
10 People found this is helpful