sync with latest changes

This commit is contained in:
--add 2016-06-06 14:46:28 +05:30
commit 1167bb6c1f
12 changed files with 959 additions and 248 deletions

View file

@ -10,7 +10,13 @@
.. link:topics/oidc/java/java-adapters.adoc[Java Adapters]
... link:topics/oidc/java/java-adapter-config.adoc[Java Adapters Config]
... link:topics/oidc/java/jboss-adapter.adoc[JBoss EAP/Wildfly Adapter]
... link:topics/oidc/java/fuse-adapter.adoc[JBoss Fuse and Apache Karaf Adapter]
... link:topics/oidc/java/fuse-adapter.adoc[JBoss Fuse Adapter]
.... link:topics/oidc/java/fuse/classic-war.adoc[Classic WAR application]
.... link:topics/oidc/java/fuse/servlet-whiteboard.adoc[Servlet Deployed as OSGI Service]
.... link:topics/oidc/java/fuse/camel.adoc[Apache Camel]
.... link:topics/oidc/java/fuse/cxf-separate.adoc[Apache CXF on Separate Jetty]
.... link:topics/oidc/java/fuse/cxf-builtin.adoc[Apache CXF on default Jetty]
.... link:topics/oidc/java/fuse/fuse-admin.adoc[Fuse Admin Services]
{% if book.community %}
... link:topics/oidc/java/tomcat-adapter.adoc[Tomcat 6, 7 and 8 Adapters]
... link:topics/oidc/java/jetty9-adapter.adoc[Jetty 9.x Adapters]
@ -29,6 +35,9 @@
.. link:topics/oidc/javascript-adapter.adoc[JavaScript Adapter]
.. link:topics/oidc/oidc-generic.adoc[Other OpenID Connect libraries]
{% if book.community %}
... link:topics/oidc/mod-auth-openidc.adoc[mod_auth_oidc Apache HTTPD Module]
{% endif %}
. link:topics/saml/saml-overview.adoc[SAML]
.. link:topics/saml/java/java-adapters.adoc[Java Adapters]

View file

@ -1,3 +1,4 @@
[[_client_registration]]
== Client Registration
In order for an application or service to utilize {{book.project.name}} it has to register a client in {{book.project.name}}.

View file

@ -1,17 +1,17 @@
[[_fuse_adapter]]
=== JBoss Fuse and Apache Karaf Adapter
=== JBoss Fuse Adapter
NOTE: JBoss Fuse is a Technology Preview feature and is not fully supported
Currently Keycloak supports securing your web applications running inside http://www.jboss.org/products/fuse/overview/[JBoss Fuse] or http://karaf.apache.org/[Apache Karaf] .
It leverages <<_jetty8_adapter,Jetty 8 adapter>> as both JBoss Fuse 6.2 and Apache Karaf 3 are bundled with http://eclipse.org/jetty/[Jetty 8.1 server]
Currently {{book.project.name}} supports securing your web applications running inside http://www.jboss.org/products/fuse/overview/[JBoss Fuse] .
It leverages <<fake/../jetty8-adapter.adoc#_jetty8_adapter,Jetty 8 adapter>> as both JBoss Fuse 6.2 are bundled with http://eclipse.org/jetty/[Jetty 8.1 server]
under the covers and Jetty is used for running various kinds of web applications.
What is supported for Fuse/Karaf is:
What is supported for Fuse is:
* Security for classic WAR applications deployed on Fuse/Karaf with https://ops4j1.jira.com/wiki/display/ops4j/Pax+Web+Extender+-+War[Pax Web War Extender].
* Security for servlets deployed on Fuse/Karaf as OSGI services with https://ops4j1.jira.com/wiki/display/ops4j/Pax+Web+Extender+-+Whiteboard[Pax Web Whiteboard Extender].
* Security for classic WAR applications deployed on Fuse with https://ops4j1.jira.com/wiki/display/ops4j/Pax+Web+Extender+-+War[Pax Web War Extender].
* Security for servlets deployed on Fuse as OSGI services with https://ops4j1.jira.com/wiki/display/ops4j/Pax+Web+Extender+-+Whiteboard[Pax Web Whiteboard Extender].
* Security for http://camel.apache.org/[Apache Camel] Jetty endpoints running with http://camel.apache.org/jetty.html[Camel Jetty] component.
* Security for http://cxf.apache.org/[Apache CXF] endpoints running on their own separate http://cxf.apache.org/docs/jetty-configuration.html[Jetty engine].
* Security for http://cxf.apache.org/[Apache CXF] endpoints running on default engine provided by CXF servlet.
@ -19,48 +19,10 @@ What is supported for Fuse/Karaf is:
==== How to secure your web applications inside Fuse
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.
Basically all mentioned web applications require to inject Keycloak Jetty authenticator into underlying Jetty server . The steps to achieve it are bit different
according to application type.
===== Classic WAR application
The needed steps are:
* Declare needed constraints in `/WEB-INF/web.xml`
* Add `jetty-web.xml` file with the authenticator to `/WEB-INF/jetty-web.xml` and add `/WEB-INF/keycloak.json` with your Keycloak configuration
* Make sure your WAR imports `org.keycloak.adapters.jetty` and maybe some more packages in MANIFEST.MF file in header `Import-Package`. It's
recommended to use maven-bundle-plugin similarly like Fuse examples are doing, but note that "*" resolution for package doesn't import `org.keycloak.adapters.jetty` package
as it's not used by application or Blueprint or Spring descriptor, but it's used just in jetty-web.xml file.
Take a look at `customer-portal-app` from fuse example for inspiration.
===== Servlet web application deployed by pax-whiteboard-extender
The needed steps are:
* Keycloak provides PaxWebIntegrationService, which allows to inject jetty-web.xml and configure security constraints for your application.
Example `product-portal-app` declares this in `OSGI-INF/blueprint/blueprint.xml` . Note that your servlet needs to depend on it.
* Steps 2,3 are same like for classic WAR
Take a look at `product-portal-app` for inspiration.
===== Apache camel application
You can secure your Apache camel endpoint using http://camel.apache.org/jetty.html[camel-jetty] endpoint by adding securityHandler with `KeycloakJettyAuthenticator` and
proper security constraints injected. Take a look at `OSGI-INF/blueprint/blueprint.xml` configuration in `camel` application on example of how it can be done in details.
===== Apache CXF endpoint
It's recommended to run your CXF endpoints secured by Keycloak on separate Jetty engine. You need to add `META-INF/spring/beans.xml` to your application
and then declare `httpj:engine-factory` with Jetty SecurityHandler with injected `KeycloakJettyAuthenticator` inside.
Fore more details, take a look at example application `cxf-ws` from Keycloak Fuse demo, which is using separate endpoint on
http://localhost:8282 . All the important configuration inside this application is declared in `META-INF/spring/beans.xml` .
Basically all mentioned web applications require to inject {{book.project.name}} Jetty authenticator into underlying Jetty server . The steps to achieve it are bit different
according to application type. The details are described in individual sub-chapters.
<<<<<<< HEAD
===== Builtin CXF web applications
Some services automatically come with deployed servlets on startup. One of such examples is CXF servlet running on
@ -102,3 +64,9 @@ It's described in details in the README file of Fuse example, which in example d
===== Secure Fuse admin console
Fuse admin console is Hawt.io. See http://hawt.io/configuration/index.html[Hawt.io documentation] for more info about how to secure it with Keycloak.
=======
{% if book.community %}
The best place to start is look at Fuse demo bundled as part of {{book.project.name}} examples in directory `fuse` . Most of the steps should be understandable from testing and
understanding the demo.
{% endif %}
>>>>>>> 163974a212546af0df02970a57466a056b83ba5a

View file

@ -0,0 +1,95 @@
[[_fuse_adapter_camel]]
==== Apache Camel Application
* You can secure your Apache camel endpoint using http://camel.apache.org/jetty.html[camel-jetty] component by adding securityHandler with `KeycloakJettyAuthenticator` and
proper security constraints injected. You can add file `OSGI-INF/blueprint/blueprint.xml` into your camel application with the configuration similar to below.
The roles, security constraint mappings and {{book.project.name}} adapter configuration may be a bit different according to your environment and needs:
[source,xml]
----
<?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="realmKey" value="MIGfMA0G..."/>
<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&amp;matchOnUriPrefix=true" />
<process ref="helloProcessor" />
<log message="The message from camel endpoint contains ${body}"/>
</route>
</camelContext>
</blueprint>
----
* The `Import-Package` in `META-INF/MANIFEST.MF` needs to contain those imports:
[source]
----
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="[8,10)",
org.eclipse.jetty.server.nio;version="[8,10)",
org.eclipse.jetty.util.security;version="[8,10)",
org.keycloak.*;version="{{book.project.version}}",
org.osgi.service.blueprint,
org.osgi.service.blueprint.container,
org.osgi.service.event,
----

View file

@ -0,0 +1,82 @@
[[_fuse_adapter_classic_war]]
==== Secure Classic WAR application
The needed steps to secure your WAR are:
* Declare needed security constraints in `/WEB-INF/web.xml` . You also need to declare login-config and all the roles inside security-role.
The example configuration can look like this:
[source,xml]
----
<?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 `jetty-web.xml` file with the authenticator to `/WEB-INF/jetty-web.xml` . Typically it will look like this:
[source,xml]
----
<?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>
----
* Add `/WEB-INF/keycloak.json` with your {{book.project.name}} configuration. The format of this config file is described
in the <<fake/../../java-adapter-config.adoc#_java_adapter_config,Java Adapters Config>> section.
* Make sure your WAR imports `org.keycloak.adapters.jetty` and maybe some more packages in `META-INF/MANIFEST.MF` file in header `Import-Package`. It's
recommended to use `maven-bundle-plugin` in your project to properly generate OSGI headers in manifest.
Note that "*" resolution for package doesn't import `org.keycloak.adapters.jetty` package
as it's not used by application or Blueprint or Spring descriptor, but it's used just in `jetty-web.xml` file. So list of the packages to import may look like this:
[source]
----
org.keycloak.adapters.jetty;version="{{book.project.version}}",
org.keycloak.adapters;version="{{book.project.version}}",
org.keycloak.constants;version="{{book.project.version}}",
org.keycloak.util;version="{{book.project.version}}",
org.keycloak.*;version="{{book.project.version}}",
*;resolution:=optional
----

View file

@ -0,0 +1,98 @@
[[_fuse_adapter_cxf_builtin]]
==== Secure Apache CXF Endpoint on default Jetty Engine
Some services automatically come with deployed servlets on startup. One of such services is CXF servlet running on
http://localhost:8181/cxf context. Securing such endpoints is quite tricky. The approach, which {{book.project.name}} is currently using,
is providing ServletReregistrationService, which undeploys builtin servlet at startup, so you are able to re-deploy it again on context secured by {{book.project.name}}.
This is how configuration file `OSGI-INF/blueprint/blueprint.xml` inside your application may look like. Note it adds JAX-RS `customerservice` endpoint,
which is endpoint specific to your application, but more importantly, it secures whole `/cxf` context.
[source,xml]
----
<?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 side effect, all other CXF services running on default CXF HTTP destination will be secured too. Similarly when the application is undeployed, then
whole `/cxf` context will become unsecured too. For this reason, it's recommended to use your own Jetty engine for your apps like
described in <<fake/../cxf-separate.adoc#_fuse_adapter_cxf_separate,Secure CXF Application on separate Jetty Engine>> as then you have more
control over security for each application individually.
* You may need to have directory `WEB-INF` inside your project (even if your project is not web application) and create files `/WEB-INF/jetty-web.xml` and
`/WEB-INF/keycloak.json` in similar way like it's in <<fake/../classic-war.adoc#_fuse_adapter_classic_war,Classic WAR application>>.
Note you don't need `web.xml` as the security-constrains are declared in blueprint configuration file.
* The `Import-Package` in `META-INF/MANIFEST.MF` needs to contain those imports:
[source]
----
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="[8,10)",
org.eclipse.jetty.util.security;version="[8,10)",
org.keycloak.*;version="{{book.project.version}}",
org.keycloak.adapters.jetty;version="{{book.project.version}}",
*;resolution:=optional
----

View file

@ -0,0 +1,111 @@
[[_fuse_adapter_cxf_separate]]
==== Secure Apache CXF Endpoint on separate Jetty
It's recommended to run your CXF endpoints secured by {{book.project.name}} on separate Jetty engine. This is the setup described in this section.
* You need to add `META-INF/spring/beans.xml` to your application and then declare `httpj:engine-factory` with Jetty SecurityHandler with
injected `KeycloakJettyAuthenticator` inside. The configuration may look like this for CXF JAX-WS application:
[source,xml]
----
<?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="realmKey" value="MIGfMA0..."/>
<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:
[source,xml]
----
<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` in `META-INF/MANIFEST.MF` needs to contain those imports:
[source]
----
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="[8,10)",
org.eclipse.jetty.util.security;version="[8,10)",
org.keycloak.*;version="{{book.project.version}}"
----

View file

@ -0,0 +1,87 @@
[[_fuse_adapter_admin]]
==== Secure Fuse Admin Services
===== SSH authentication to Fuse terminal
{{book.project.name}} mainly addresses usecases for authentication of web applications, however if your other web services and applications are protected
with {{book.project.name}}, it may be good to protect non-web admin services like SSH with {{book.project.name}} credentials too. It's possible to do it
by using JAAS login module, which allows to remotely connect to {{book.project.name}} and verify credentials based on
<<fake/../../../oidc-generic.adoc#_resource_owner_password_credentials_flow,Resource Owner Password Credentials>> .
Example steps for enable SSH authentication:
* In {{book.project.name}} you need to create client (assume it's called `ssh-jmx-admin-client`), which will be used for SSH authentication.
This client needs to have switch `Direct grant enabled` to true.
* You need to update/specify this property in file `$FUSE_HOME/etc/org.apache.karaf.shell.cfg`:
[source]
----
sshRealm=keycloak
----
* Add file `$FUSE_HOME/etc/keycloak-direct-access.json` with the content similar to this (change based on your environment and {{book.project.name}} client settings):
[source,json]
----
{
"realm": "demo",
"resource": "ssh-jmx-admin-client",
"realm-public-key": "MIGfMA...",
"ssl-required" : "external",
"auth-server-url" : "http://localhost:8080/auth",
"credentials": {
"secret": "password"
}
}
----
This file contains configuration of the client application, which is used by JAAS DirectAccessGrantsLoginModule from `keycloak` JAAS realm for SSH authentication.
* Start Fuse and install `keycloak` JAAS realm into Fuse. This could be done easily by installing `keycloak-jaas` feature, which has JAAS realm predefined
(you are able to override it by using your own `keycloak` JAAS realm with higher ranking). Use those commands in Fuse terminal:
```
features:addurl mvn:org.keycloak/keycloak-osgi-features/{{book.project.version}}/xml/features
features:install keycloak-jaas
```
* Now let's type this from your terminal to login via SSH as `admin` user:
```
ssh -o PubkeyAuthentication=no -p 8101 admin@localhost
```
And login with password `password` . Note that your user needs to have realm role `admin` . The required roles are configured in `$FUSE_HOME/etc/org.apache.karaf.shell.cfg`
===== JMX authentication
This may be needed in case if you really want to use jconsole or other external tool to perform remote connection to JMX through RMI. Otherwise it may
be better to use just hawt.io/jolokia as jolokia agent is installed in hawt.io by default.
* In file `$FUSE_HOME/etc/org.apache.karaf.management.cfg` you can change this property:
[source]
----
jmxRealm=keycloak
----
* You need `keycloak-jaas` feature and file `$FUSE_HOME/etc/keycloak-direct-access.json` as described in SSH section above.
* In jconsole you can fill URL like:
[source]
----
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)
Note again that users without `admin` role are not able to login as they are not authorized. However users with access to Hawt.io admin console
may be still able to access MBeans remotely via HTTP (Hawtio). So make sure to protect Hawt.io web console with same roles like JMX through RMI to
really protect JMX mbeans.
===== Secure Fuse admin console
Fuse admin console is Hawt.io. See http://hawt.io/configuration/index.html[Hawt.io documentation] for more info about how to secure it with {{book.project.name}}.

View file

@ -0,0 +1,76 @@
[[_fuse_adapter_servlet_whiteboard]]
==== Secure Servlet deployed as OSGI service
This is useful for the case, when you have sevlet class inside your OSGI bundle project, which is not deployed as classic WAR. Fuse uses
https://ops4j1.jira.com/wiki/display/ops4j/Pax+Web+Extender+-+Whiteboard[Pax Web Whiteboard Extender] for deploy such servlet as web application.
The needed steps to secure your servlet with {{book.project.name}} are:
* Keycloak provides PaxWebIntegrationService, which allows to inject jetty-web.xml and configure security constraints for your application.
You need to declare such service in `OSGI-INF/blueprint/blueprint.xml` inside your application. Note that your servlet needs to depend on it.
The example configuration can look like this:
[source,xml]
----
<?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 may need to have directory `WEB-INF` inside your project (even if your project is not web application) and create files `/WEB-INF/jetty-web.xml` and
`/WEB-INF/keycloak.json` in similar way like it's in <<fake/../classic-war.adoc#_fuse_adapter_classic_war,Classic WAR application>>.
Note you don't need `web.xml` as the security-constrains are declared in blueprint configuration file.
* The `Import-Package` in `META-INF/MANIFEST.MF` needs to contain at least those imports:
[source]
----
org.keycloak.adapters.jetty;version="{{book.project.version}}",
org.keycloak.adapters;version="{{book.project.version}}",
org.keycloak.constants;version="{{book.project.version}}",
org.keycloak.util;version="{{book.project.version}}",
org.keycloak.*;version="{{book.project.version}}",
*;resolution:=optional
----

View file

@ -1,28 +1,31 @@
[[_javascript_adapter]]
== Javascript Adapter
The Keycloak Server comes with a Javascript library you can use to secure HTML/Javascript applications.
This library is referenceable directly from the keycloak server.
You can also download the adapter from Keycloak's download site if you want a static copy.
It works in the same way as other application adapters except that your browser is driving the OAuth redirect protocol rather than the server.
{{book.project.name}} 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 disadvantage of using this approach is that you have a non-confidential, public client.
This makes it more important that you register valid redirect URLs and make sure your domain name is secured.
The library can be retrieved directly from the {{book.project.name}} server at `/auth/js/keycloak.js` and is also distributed as a ZIP archive.
To use this adapter, you must first configure an application (or client) through the `Keycloak Admin Console`.
You should select `public` for the `Access Type` field.
As public clients can't be verified with a client secret, you are required to configure one or more valid redirect uris.
Once you've configured the application, click on the `Installation` tab and download the `keycloak.json` file.
This file should be hosted on your web-server at the same root as your HTML pages.
Alternatively, you can manually configure the adapter and specify the URL for this file.
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.
Next, you have to initialize the adapter in your application.
An example is shown below.
To use the JavaScript adapter you must first create a client for your application in the {{book.project.name}} Administration Console. Make sure `public`
is selected for `Access Type`.
You also need to configure valid redirect URIs and valid web origins. Be as specific as possible as failing to do so may results in a security vulnerability.
Once the client is created click on the `Installation` tab select `Keycloak OIDC JSON` for `Format Option` then click on `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:
[source,html]
----
<head>
<script src="http://<keycloak server>/auth/js/keycloak.js"></script>
<script src="keycloak.js"></script>
<script>
var keycloak = Keycloak();
keycloak.init().success(function(authenticated) {
@ -33,144 +36,135 @@ An example is shown below.
</script>
</head>
----
To specify the location of the keycloak.json file:
[source]
If the `keycloak.json` file is in a different location you can specify it:
[source,javascript]
----
var keycloak = Keycloak('http://localhost:8080/myapp/keycloak.json'));
----
Or finally to manually configure the adapter:
[source]
----
You can also skip the file altogether and manually configure the adapter:
[source,javascript]
----
var keycloak = Keycloak({
url: 'http://keycloak-server/auth',
realm: 'myrealm',
clientId: 'myapp'
});
----
You can also pass `login-required` or `check-sso` to the init function.
Login required will cause a redirect to the login form on the server, while check-sso will simply redirect to the auth server to check if the user is already logged in to the realm.
For example:
----
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 {{book.project.name}}
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.
To enable `login-required` set `onLoad` to `login-required` and pass to the init method:
[source]
----
keycloak.init({ onLoad: 'login-required' })
----
After you login, your application will be able to make REST calls using bearer token authentication.
Here's an example pulled from the `customer-portal-js` example that comes with the distribution.
After the user is authenticated the application can make requests to RESTful services secured by {{book.project.name}} by including the bearer token in the
`Authorization` header. For example:
[source]
[source,javascript]
----
<script>
var loadData = function () {
document.getElementById('username').innerText = keycloak.username;
var loadData = function () {
document.getElementById('username').innerText = keycloak.username;
var url = 'http://localhost:8080/database/customers';
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);
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) {
var users = JSON.parse(req.responseText);
var html = '';
for (var i = 0; i < users.length; i++) {
html += '<p>' + users[i] + '</p>';
}
document.getElementById('customers').innerHTML = html;
console.log('finished loading data');
}
req.onreadystatechange = function () {
if (req.readyState == 4) {
if (req.status == 200) {
alert('Success');
} else if (req.status == 403) {
alert('Forbidden');
}
}
req.send();
};
var loadFailure = function () {
document.getElementById('customers').innerHTML = '<b>Failed to load data. Check console log</b>';
};
var reloadData = function () {
keycloak.updateToken().success(loadData).error(loadFailure);
}
</script>
<button onclick="reloadData()">Submit</button>
----
req.send();
};
----
The `loadData()` method builds an HTTP request setting the `Authorization` header to a bearer token.
The `keycloak.token` points to the access token the browser obtained when it logged you in.
The `loadFailure()` method is invoked on a failure.
The `reloadData()` function calls `keycloak.updateToken()` passing in the `loadData()` and `loadFailure()` callbacks.
The `keycloak.updateToken()` method checks to see if the access token hasn't expired.
If it hasn't, and your oauth login returned a refresh token, this method will refresh the access token.
Finally, if successful, it will invoke the success callback, which in this case is the `loadData()` method.
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 object which makes it easy to invoke the service only if the
token was successfully refreshed and for example display an error to the user if it wasn't. For example:
To refresh the token when it is expired, call the `updateToken` method.
This method returns a promise object, which can be used to invoke a function on success or failure.
This method can be used to wrap functions that should only be called with a valid token.
For example, the following method will refresh the token if it expires within 30 seconds, and then invoke the specified function.
If the token is valid for more than 30 seconds it will just call the specified function.
[source]
[source,javascript]
----
keycloak.updateToken(30).success(function() {
// send request with valid token
loadData();
}).error(function() {
alert('failed to refresh token');
alert('Failed to refresh token');
);
----
== Session status iframe
By default, the JavaScript adapter creates a non-visible 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 from a special status cookie.
This feature can be disabled by setting `checkLoginIframe: false` in the options passed to the `init` method.
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. It's format can change and it's also associated with the URL of the {{book.project.name}} server, not
your application.
[[_javascript_implicit_flow]]
== Implicit and Hybrid Flow
By default, the JavaScript adapter uses http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth[OpenID Connect standard (Authorization code) flow], which means that after authentication, the Keycloak server redirects the user back to your application, where the JavaScript adapter will exchange the `code` for an access token and a refresh token.
By default, the JavaScript adapter uses the http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth[Authorization Code] flow.
With this flow the {{book.project.name}} server returns a authorization code, not a 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.
However, Keycloak also supports http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth[OpenID Connect Implicit flow] where an access token is sent immediately after successful authentication with Keycloak (there is no additional request for exchange code). This could have better performance than standard flow, as there is no additional request to exchange the code for tokens.
However, sending the access token in the URL fragment could pose a security issue in some environments (access logs might expose tokens located in the URL).
{{book.project.name}} also supports the http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth[Implicit] flow where an access token
is sent immediately after successful authentication with {{book.project.name}}. 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.
To enable implicit flow, you need to enable the `Implicit Flow Enabled` flag for the client in the Keycloak admin console.
You also need to pass the parameter `flow` with value `implicit` to `init` method.
An example is below:
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.
[source]
To enable implicit flow, you need to enable the `Implicit Flow Enabled` flag for the client in the {{book.project.name}} Administration Console.
You also need to pass the parameter `flow` with value `implicit` to `init` method:
[source,javascript]
----
keycloak.init({ flow: 'implicit' })
----
keycloak.init({ flow: 'implicit' })
----
Note that with implicit flow, you are not given a refresh token after authentication.
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 {{book.project.name}} again to obtain a new access token.
This makes it harder for your application to periodically update the access token in background (without browser redirection). It's recommended that you implement an `onTokenExpired` callback method on the keycloak object, so you are notified after the token is expired (For example you can call keycloak.login, which will redirect browser to Keycloak login screen and it will immediately redirect you back if the SSO session is still valid and the user is still logged.
However, make sure to save the application state before performing a redirect.)
Keycloak also has support for http://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth[OpenID Connect Hybrid flow].
{{book.project.name}} also supports the http://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth[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 {{book.project.name}} 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 security risks might still apply.
However, one advantage over the implicit flow is that a refresh token is made available to the application (after the code-to-token request is finished).
But, the token is still sent in the URL, and the security vulnerability mentioned earlier may still apply.
For hybrid flow, you need to pass the parameter `flow` with value `hybrid` to `init` method.
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:
[source,javascript]
----
keycloak.init({ flow: 'hybrid' })
----
== Older browsers
The JavaScript adapter depends on Base64 (window.btoa and window.atob) and HTML5 History API.
If you need to support browsers that don't provide those (for example IE9) you'll need to add polyfillers.
If you need to support browsers that don't have these available (for example IE9) you need to add polyfillers.
Example polyfill libraries:
* https://github.com/davidchambers/Base64.js
@ -190,20 +184,48 @@ new Keycloak({ url: 'http://localhost/auth', realm: 'myrealm', clientId: 'myApp'
=== Properties
* authenticated - true if the user is authenticated
* Authorization
* tokenParsed - the parsed token
* subject - the user id
* idToken - the id token if claims is enabled for the application, null otherwise
* idTokenParsed - the parsed id token
* realmAccess - the realm roles associated with the token
* resourceAccess - the resource roles assocaited with the token
* refreshToken - the base64 encoded token that can be used to retrieve a new token
* refreshTokenParsed - the parsed refresh token
* timeSkew - estimated skew between local time and Keycloak server in seconds
* fragment
* Implicit flow
* flow
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 assocaited 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 {{book.project.name}} 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.
responseType::
Response type sent to {{book.project.name}} with login requests. This is determined based on the flow value used during initialization, but can be overridden by setting this value.
=== Methods
@ -213,100 +235,92 @@ Called to initialize the adapter.
Options is an Object, where:
* onLoad - specifies an action to do on load, can be either 'login-required' or 'check-sso'
* 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)
* query
+`fragment`
+`fragment`
+`query`
* standard
+`implicit`
+`hybrid`<<_javascript_implicit_flow,+Implicit flow>>
* onLoad - Specifies an action to do on load. Supported values are 'login-required' or 'check-sso'.
* 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 or fragment . Default value is fragment, 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 over query.
* flow - Set the OpenID Connect flow. Valid values are standard, implicit or hybrid.
Returns promise to set functions to be invoked on success or error.
==== login(options)
Redirects to login form on (options is an optional object with redirectUri and/or prompt fields)
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 - can be set to 'none' to check if the user is logged in already (if not logged in, a login form is not displayed)
* loginHint - used to pre-fill the username/email field on the login form
* action - if value is 'register' then user is redirected to registration page, otherwise to login page
* locale - specifies the desired locale for the UI
* redirectUri - Specifies the uri to redirect to after login.
* prompt - Can be set to 'none' to check if the user is logged in already (if not logged in, a login form is not displayed).
* loginHint - Used to pre-fill the username/email field on the login form.
* action - If value is 'register' then user is redirected to registration page, otherwise to login page.
* locale - Specifies the desired locale for the UI.
==== createLoginUrl(options)
Returns the url to login form on (options is an optional object with redirectUri and/or prompt fields)
Returns the URL 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 - can be set to 'none' to check if the user is logged in already (if not logged in, a login form is not displayed)
* redirectUri - Specifies the uri to redirect to after login.
* prompt - Can be set to 'none' to check if the user is logged in already (if not logged in, a login form is not displayed).
==== logout(options)
Redirects to logout
Redirects to logout.
Options is an Object, where:
* redirectUri - specifies the uri to redirect to after logout
* redirectUri - Specifies the uri to redirect to after logout.
==== createLogoutUrl(options)
Returns logout out
Returns the URL to logout the user.
Options is an Object, where:
* redirectUri - specifies the uri to redirect to after logout
* redirectUri - Specifies the uri to redirect to after logout.
==== register(options)
Redirects to registration form.
It's a shortcut for doing login with option action = 'register'
Redirects to registration form. Shortcut for login with option action = 'register'
Options are same as login method but 'action' is overwritten to 'register'
Options are same as login method but 'action' is set to 'register'
==== createRegisterUrl(options)
Returns the url to registration page.
It's a shortcut for doing createRegisterUrl with option action = 'register'
Returns the url to registration page. Shortcut for createLoginUrl with option action = 'register'
Options are same as createLoginUrl method but 'action' is overwritten to 'register'
Options are same as createLoginUrl method but 'action' is set to 'register'
==== accountManagement()
Redirects to account management
Redirects to the Account Management Console.
==== createAccountUrl()
Returns the url to account management
Returns the URL to the Account Management Console.
==== hasRealmRole(role)
Returns true if the token has the given realm 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)
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
Loads the users profile.
Returns promise to set functions to be invoked on success or error.
==== 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)
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)
@ -316,48 +330,42 @@ If the session status iframe is enabled, the session status is also checked.
Returns promise to set functions that can be invoked if the token is still valid, or if the token is no longer valid.
For example:
[source]
[source,javascript]
----
keycloak.updateToken(5).success(function(refreshed) {
if (refreshed) {
alert('token was successfully refreshed');
alert('Token was successfully refreshed');
} else {
alert('token is still valid');
alert('Token is still valid');
}
}).error(function() {
alert('failed to refresh the token, or the session has expired');
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 has expired, for example if updating token fails.
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.
[source]
----
keycloak.updateToken(5).error(function() {
keycloak.clearToken();
});
----
=== Callback Events
The adapter supports setting callback listeners for certain events.
For example:
[source]
----
keycloak.onAuthSuccess = function() { alert('authenticated'); }
----
----
* 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 access token expired. When this happens you can for example refresh token, or if refresh not available (ie. with implicit flow) you can redirect to login screen
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 access token expired. When this happens you can for example refresh token, or if refresh not available (ie. with implicit flow) you can redirect to login screen.

View file

@ -0,0 +1,49 @@
[[_mod_auth_openidc]]
=== mod_auth_openidc Apache HTTPD Module
The https://github.com/pingidentity/mod_auth_openidc[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.
[source,xml]
----
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 https://github.com/pingidentity/mod_auth_openidc[mod_auth_openidc]
project page.

View file

@ -1,47 +1,174 @@
== Other OpenID Connect libraries
OAuth2 https://tools.ietf.org/html/rfc6749
OpenID Connect http://openid.net/connect/
{{book.project.name}} can be secured by supplied adapters that usually are easier to use and provide better integration with {{book.project.name}}. However,
if there is no adapter available for your programming language, framework or platform you may opt to use a generic OpenID Connect Resource Provider (RP) library
instead. This chapter describes details specific to {{book.project.name}} and doesn't go into low-level details of the protocols. For more details refer to the
http://openid.net/connect/[OpenID Connect specifications] and https://tools.ietf.org/html/rfc6749[OAuth2 specification].
=== Endpoints
TODO
The most important endpoint to know is the `well-known` configuration endpoint. It lists endpoints and other configuration options relevant to the OpenID
Connect implementation in {{book.project.name}}. The endpoint is:
....
/realms/REALM-NAME/.well-known/openid-configuration
....
To get the full URL add the base URL for {{book.project.name}} 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 will retrieve all required endpoints from this endpoint, but for others you may need to list the endpoints individually.
==== Authorization Endpoint
....
/realms/master/protocol/openid-connect/auth
....
Performs authentication of the end-user. This is done by redirecting user agent to this endpoint.
For more details see http://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint[Authorization Endpoint] section in OpenID Connect specification.
==== Token Endpoint
....
/realms/master/protocol/openid-connect/token
....
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 http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint[Token Endpoint] section in OpenID Connect specification.
==== Userinfo Endpoint
....
/realms/master/protocol/openid-connect/userinfo
....
Returns standard claims about the authenticated user. Protected by a bearer token.
For more details see http://openid.net/specs/openid-connect-core-1_0.html#UserInfo[Userinfo Endpoint] section in OpenID Connect specification.
==== Logout Endpoint
....
/realms/master/protocol/openid-connect/logout
....
Logs out the authenticated user.
User agent can be redirected to the endpoint in which case the active user session will be logged out. Afterwards 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 credentials
required to authenticate the client.
==== Certificate Endpoint
....
/realms/master/protocol/openid-connect/certs
....
Public key used by realm encoded as a JSON Web Key (JWK). This key can be used to verify tokens issued by {{book.project.name}} without making invocations to
the server.
For more details see https://tools.ietf.org/html/rfc7517[JSON Web Key specification].
==== Introspection Endpoint
....
/realms/master/protocol/openid-connect/token/introspect
....
Used to retrieve the active state of a token. Protected by a bearer token and can only be invoked by confidential clients.
For more details see https://tools.ietf.org/html/rfc7662[OAuth 2.0 Token Introspection specification].
==== Dynamic Client Registration Endpoint
....
/realms/master/clients-registrations/openid-connect
....
Used to dynamically register clients.
For more details see <<fake/../../client-registration.adoc#_client_registration,Client Registration chapter>> and the
https://openid.net/specs/openid-connect-registration-1_0.html[OpenID Connect Dynamic Client Registration specification].
=== Flows
==== Authorization Grant
==== Authorization Code
The Authorization Code flow redirects the user agent to {{book.project.name}}. Once the user has successfully authenticated with {{book.project.name}} an
Authorization Code is created and the user agent is redirected back to the application. The application then uses the authorization code to along with its
credentials to obtain an Access Roken, Refresh Token and ID Token from {{book.project.name}}.
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.
For more details refer to the http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth[Authorization Code Flow] in the OpenID Connect specification.
==== Implicit
[[_resource_owner_password_credentials_flow]]
The Implicit flow redirects works similarly to the Authorization Code flow, but instead of returning a 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 http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth[Implicit Flow] in the OpenID Connect specification.
==== Resource Owner Password Credentials
Resource Owner Password Credentials, referred to as Direct Grant in {{book.project.name}}, 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.)
This flow is not included in OpenID Connect, but is a part of the OAuth 2.0 specification.
For more details refer to the https://tools.ietf.org/html/rfc6749#section-4.3[Resource Owner Password Credentials Grant] chapter in the OAuth 2.0 specification.
==== 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.
{{book.project.name}} 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.
For more details refer to the https://tools.ietf.org/html/rfc6749#section-4.4[Client Credentials Grant] chapter in the OAuth 2.0 specification.
=== Redirect URIs
Keycloak provides two special redirect uris for installed applications.
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 {{book.project.name}} 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:
[[_installed_applications_url]]
==== Installed Applications url
`http://localhost`::
http://localhost
This returns the code to a web server on the client as a query parameter.
Any port number is allowed.
This makes it possible to start a web server for the installed application on any free port number without requiring changes in the `Admin Console`.
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.
[[_installed_applications_urn]]
==== Installed Applications urn
`urn:ietf:wg:oauth:2.0:oob`::
`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.
=== Session Management
=== Dynamic Client Registration
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.