411 lines
No EOL
22 KiB
XML
Executable file
411 lines
No EOL
22 KiB
XML
Executable file
<section id="javascript-adapter">
|
|
<title>Javascript Adapter</title>
|
|
<para>
|
|
The Keycloak Server comes with a Javascript library you can use to secure HTML/Javascript applications. This
|
|
library is referencable directly from the keycloak server. You can also download the adapter from Keycloak's download
|
|
site if you want a static copy of this library. It
|
|
works in the same way as other application adapters except that your browser is driving the OAuth redirect protocol
|
|
rather than the server.
|
|
</para>
|
|
<para>
|
|
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.
|
|
</para>
|
|
<para>
|
|
To use this adapter, you must first configure an application (or client) through the <literal>Keycloak Admin Console</literal>.
|
|
You should select <literal>public</literal> for the <literal>Client Type</literal> field. As public clients can't
|
|
be verified with a client secret you are required to configure one or more valid redirect uris as well.
|
|
Once you've configured the application click on the <literal>Installation</literal> tab and download the <literal>keycloak.json</literal>
|
|
file. This file should be hosted in your web-server at the same root as your HTML pages. Alternatively you can either
|
|
specify the URL for this file, or manually configure the adapter.
|
|
</para>
|
|
<para>
|
|
Next you have to initialize the adapter in your application. An example on how to do this is shown below.
|
|
<programlisting><![CDATA[
|
|
<head>
|
|
<script src="http://<keycloak server>/auth/js/keycloak.js"></script>
|
|
<script>
|
|
var keycloak = Keycloak();
|
|
keycloak.init().success(function(authenticated) {
|
|
alert(authenticated ? 'authenticated' : 'not authenticated');
|
|
}).error(function() {
|
|
alert('failed to initialize');
|
|
});
|
|
</script>
|
|
</head>
|
|
]]></programlisting>
|
|
To specify the location of the keycloak.json file:
|
|
<programlisting><![CDATA[
|
|
var keycloak = Keycloak('http://localhost:8080/myapp/keycloak.json'));
|
|
]]></programlisting>
|
|
Or finally to manually configure the adapter:
|
|
<programlisting><![CDATA[
|
|
var keycloak = Keycloak({
|
|
url: 'http://keycloak-server/auth',
|
|
realm: 'myrealm',
|
|
clientId: 'myapp'
|
|
});
|
|
]]></programlisting>
|
|
You can also pass <literal>login-required</literal> or <literal>check-sso</literal> to the init function. Login
|
|
required will redirect to the login form on the server, while check-sso will redirect to the auth server to check
|
|
if the user is already logged in to the realm. For example:
|
|
<programlisting><![CDATA[
|
|
keycloak.init({ onLoad: 'login-required' })
|
|
]]></programlisting>
|
|
</para>
|
|
|
|
<para>
|
|
After you login, your application will be able to make REST calls using bearer token authentication. Here's
|
|
an example pulled from the <literal>customer-portal-js</literal> example that comes with the distribution.
|
|
<programlisting><![CDATA[
|
|
<script>
|
|
var loadData = function () {
|
|
document.getElementById('username').innerText = keycloak.username;
|
|
|
|
var url = 'http://localhost:8080/database/customers';
|
|
|
|
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.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>
|
|
]]></programlisting>
|
|
</para>
|
|
<para>
|
|
The <literal>loadData()</literal> method builds an HTTP request setting the <literal>Authorization</literal>
|
|
header to a bearer token. The <literal>keycloak.token</literal> points to the access token the browser obtained
|
|
when it logged you in. The <literal>loadFailure()</literal> method is invoked on a failure. The <literal>reloadData()</literal>
|
|
function calls <literal>keycloak.updateToken()</literal> passing in the <literal>loadData()</literal> and
|
|
<literal>loadFailure()</literal> callbacks. The <literal>keycloak.updateToken()</literal> 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 <literal>loadData()</literal> method.
|
|
</para>
|
|
|
|
<para>
|
|
To refresh the token if it's expired call the <literal>updateToken</literal> 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.
|
|
<programlisting><![CDATA[
|
|
keycloak.updateToken(30).success(function() {
|
|
// send request with valid token
|
|
}).error(function() {
|
|
alert('failed to refresh token');
|
|
);
|
|
]]></programlisting>
|
|
</para>
|
|
|
|
<section>
|
|
<title>Session status iframe</title>
|
|
|
|
<para>
|
|
By default the JavaScript adapter creates a non-visible iframe that is used to detect if a single-sign out has occured.
|
|
This does not require any network traffic, instead the status is retrieved from a special status cookie. This feature can be disabled
|
|
by setting <literal>checkLoginIframe: false</literal> in the options passed to the <literal>init</literal>
|
|
method.
|
|
</para>
|
|
</section>
|
|
|
|
<section id="javascript-implicit-flow">
|
|
<title>Implicit and Hybrid Flow</title>
|
|
|
|
<para>
|
|
By default the JavaScript adapter uses <ulink url="http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth">OpenID Connect standard (Authorization code) flow</ulink>, which
|
|
means that after authentication will Keycloak server redirects back to your application and Javascript adapter will exchange <literal>code</literal> for access token and refresh token.
|
|
</para>
|
|
<para>
|
|
However Keycloak also supports <ulink url="http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth">OpenID Connect Implicit flow</ulink>
|
|
where access token is directly sent after successful authentication from Keycloak and there is no additional request for exchange code.
|
|
This might have better performance than standard flow as there is no additional request for exchange code-to-token. However sending access token
|
|
in URL fragment might be security issue in some environments (Token can be more easily stolen from the network etc).
|
|
</para>
|
|
<para>To enable implicit flow, you need to enable the flag <literal>Implicit Flow Enabled</literal> for the client in Keycloak admin console. You also need to pass
|
|
the parameter <literal>flow</literal> with value <literal>implicit</literal> to <literal>init</literal> method, so that Javascript adapter will use implicit flow instead of standard flow.
|
|
The example is here:
|
|
<programlisting><![CDATA[
|
|
keycloak.init({ flow: 'implicit' })
|
|
]]></programlisting>
|
|
Note that with implicit flow, you don't have refresh token available after authentication. This makes it harder for your application to periodically update
|
|
access token in background (without browser redirection). It's recommended that you implement <literal>onTokenExpired</literal> callback method on keycloak object, so you
|
|
have possibility to do something after 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 SSO session is still valid and user is still logged. However make sure to save the application state before doing redirect.)
|
|
</para>
|
|
<para>
|
|
Keycloak also have support for <ulink url="http://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth">OpenID Connect Hybrid flow</ulink>. This requires
|
|
that client in admin console has both flags <literal>Standard Flow Enabled</literal> and <literal>Implicit Flow Enabled</literal> enabled in admin console.
|
|
The Keycloak will send both the code and tokens to your application. Access token can be immediately used and in the meantime, code can be exchanged for access token and refresh token.
|
|
Hybrid flow is good for performance similarly like implicit flow, because access token is available immediatelly to your application. But similarly like implicit flow, the token is
|
|
sent in URL fragment, so security may not be so good.
|
|
One advantage over implicit flow is, that you have also refresh token available in your application (after code-to-token request is finished in background).
|
|
</para>
|
|
<para>
|
|
For hybrid flow, you need to pass the parameter <literal>flow</literal> with value <literal>hybrid</literal> to <literal>init</literal> method.
|
|
</para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Older browsers</title>
|
|
|
|
<para>
|
|
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. Example polyfill
|
|
libraries:
|
|
|
|
<itemizedlist>
|
|
<listitem>Base64 - <ulink url="https://github.com/davidchambers/Base64.js">https://github.com/davidchambers/Base64.js</ulink></listitem>
|
|
<listitem>HTML5 History - <ulink url="https://github.com/devote/HTML5-History-API">https://github.com/devote/HTML5-History-API</ulink></listitem>
|
|
</itemizedlist>
|
|
</para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>JavaScript Adapter reference</title>
|
|
|
|
<section>
|
|
<title>Constructor</title>
|
|
<programlisting><![CDATA[
|
|
new Keycloak();
|
|
new Keycloak('http://localhost/keycloak.json');
|
|
new Keycloak({ url: 'http://localhost/auth', realm: 'myrealm', clientId: 'myApp' });
|
|
]]></programlisting>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Properties</title>
|
|
|
|
<itemizedlist>
|
|
<listitem>authenticated - true if the user is authenticated</listitem>
|
|
<listitem>token - the base64 encoded token that can be sent in the <literal>Authorization</literal> header in requests to services</listitem>
|
|
<listitem>tokenParsed - the parsed token</listitem>
|
|
<listitem>subject - the user id</listitem>
|
|
<listitem>idToken - the id token if claims is enabled for the application, null otherwise</listitem>
|
|
<listitem>idTokenParsed - the parsed id token</listitem>
|
|
<listitem>realmAccess - the realm roles associated with the token</listitem>
|
|
<listitem>resourceAccess - the resource roles assocaited with the token</listitem>
|
|
<listitem>refreshToken - the base64 encoded token that can be used to retrieve a new token</listitem>
|
|
<listitem>refreshTokenParsed - the parsed refresh token</listitem>
|
|
<listitem>timeSkew - estimated skew between local time and Keycloak server in seconds</listitem>
|
|
<listitem>responseMode - responseMode passed during initialization. See below for details. Default value is <literal>fragment</literal></listitem>
|
|
<listitem>flow - OpenID Connect flow passed during initialization. See <link linkend="javascript-implicit-flow">Implicit flow</link> for details.</listitem>
|
|
<listitem>responseType - responseType used for send to Keycloak server at login request. This is determined based on the <literal>flow</literal> value used during initialization,
|
|
but you have possibility to override it by directly set this value</listitem>
|
|
</itemizedlist>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Methods</title>
|
|
|
|
<simplesect>
|
|
<title>init(options)</title>
|
|
|
|
<para>Called to initialize the adapter.</para>
|
|
<para>Options is an Object, where:
|
|
<itemizedlist>
|
|
<listitem>onLoad - specifies an action to do on load, can be either 'login-required' or 'check-sso'</listitem>
|
|
<listitem>token - set an initial value for the token</listitem>
|
|
<listitem>refreshToken - set an initial value for the refresh token</listitem>
|
|
<listitem>checkLoginIframe - set to enable/disable monitoring login state (default is true)</listitem>
|
|
<listitem>checkLoginIframeInterval - set the interval to check login state (default is 5 seconds)</listitem>
|
|
<listitem>responseMode - set the OpenID Connect response mode send to Keycloak server at login request. Valid values are <literal>query</literal> or <literal>fragment</literal> .
|
|
Default value is <literal>fragment</literal>, 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 <literal>query</literal>.
|
|
</listitem>
|
|
<listitem>flow - set the OpenID Connect flow. Valid values are <literal>standard</literal>, <literal>implicit</literal> or <literal>hybrid</literal>.
|
|
See <link linkend="javascript-implicit-flow">Implicit flow</link> for details.</listitem>
|
|
</itemizedlist>
|
|
</para>
|
|
<para>Returns promise to set functions to be invoked on success or error.</para>
|
|
</simplesect>
|
|
|
|
<simplesect>
|
|
<title>login(options)</title>
|
|
|
|
<para>Redirects to login form on (options is an optional object with redirectUri and/or prompt fields)</para>
|
|
<para>Options is an Object, where:
|
|
<itemizedlist>
|
|
<listitem>redirectUri - specifies the uri to redirect to after login</listitem>
|
|
<listitem>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)</listitem>
|
|
<listitem>loginHint - used to pre-fill the username/email field on the login form</listitem>
|
|
<listitem>action - if value is 'register' then user is redirected to registration page, otherwise to login page</listitem>
|
|
<listitem>locale - specifies the desired locale for the UI</listitem>
|
|
</itemizedlist>
|
|
</para>
|
|
</simplesect>
|
|
<simplesect>
|
|
<title>createLoginUrl(options)</title>
|
|
|
|
<para>Returns the url to login form on (options is an optional object with redirectUri and/or prompt fields)</para>
|
|
<para>Options is an Object, where:
|
|
<itemizedlist>
|
|
<listitem>redirectUri - specifies the uri to redirect to after login</listitem>
|
|
<listitem>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)</listitem>
|
|
</itemizedlist>
|
|
</para>
|
|
</simplesect>
|
|
|
|
<simplesect>
|
|
<title>logout(options)</title>
|
|
|
|
<para>Redirects to logout</para>
|
|
<para>Options is an Object, where:
|
|
<itemizedlist>
|
|
<listitem>redirectUri - specifies the uri to redirect to after logout</listitem>
|
|
</itemizedlist>
|
|
</para>
|
|
</simplesect>
|
|
|
|
<simplesect>
|
|
<title>createLogoutUrl(options)</title>
|
|
|
|
<para>Returns logout out</para>
|
|
<para>Options is an Object, where:
|
|
<itemizedlist>
|
|
<listitem>redirectUri - specifies the uri to redirect to after logout</listitem>
|
|
</itemizedlist>
|
|
</para>
|
|
</simplesect>
|
|
|
|
<simplesect>
|
|
<title>register(options)</title>
|
|
|
|
<para>Redirects to registration form. It's a shortcut for doing login with option action = 'register'</para>
|
|
<para>Options are same as login method but 'action' is overwritten to 'register'</para>
|
|
</simplesect>
|
|
|
|
<simplesect>
|
|
<title>createRegisterUrl(options)</title>
|
|
|
|
<para>Returns the url to registration page. It's a shortcut for doing createRegisterUrl with option action = 'register'</para>
|
|
<para>Options are same as createLoginUrl method but 'action' is overwritten to 'register'</para>
|
|
</simplesect>
|
|
|
|
<simplesect>
|
|
<title>accountManagement()</title>
|
|
|
|
<para>Redirects to account management</para>
|
|
</simplesect>
|
|
|
|
<simplesect>
|
|
<title>createAccountUrl()</title>
|
|
|
|
<para>Returns the url to account management</para>
|
|
</simplesect>
|
|
|
|
<simplesect>
|
|
<title>hasRealmRole(role)</title>
|
|
|
|
<para>Returns true if the token has the given realm role</para>
|
|
</simplesect>
|
|
|
|
<simplesect>
|
|
<title>hasResourceRole(role, resource)</title>
|
|
|
|
<para>Returns true if the token has the given role for the resource (resource is optional, if not specified clientId is used)</para>
|
|
</simplesect>
|
|
|
|
<simplesect>
|
|
<title>loadUserProfile()</title>
|
|
|
|
<para>Loads the users profile</para>
|
|
|
|
<para>Returns promise to set functions to be invoked on success or error.</para>
|
|
</simplesect>
|
|
|
|
<simplesect>
|
|
<title>isTokenExpired(minValidity)</title>
|
|
|
|
<para>Returns true if the token has less than minValidity seconds left before it expires (minValidity is optional, if not specified 0 is used)</para>
|
|
</simplesect>
|
|
|
|
<simplesect>
|
|
<title>updateToken(minValidity)</title>
|
|
|
|
<para>If the token expires within minValidity seconds (minValidity is optional, if not specified 0 is used) the token is refreshed.
|
|
If the session status iframe is enabled, the session status is also checked.
|
|
</para>
|
|
|
|
<para>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:</para>
|
|
|
|
<programlisting><![CDATA[
|
|
keycloak.updateToken(5).success(function(refreshed) {
|
|
if (refreshed) {
|
|
alert('token was successfully refreshed');
|
|
} else {
|
|
alert('token is still valid');
|
|
}
|
|
}).error(function() {
|
|
alert('failed to refresh the token, or the session has expired');
|
|
});
|
|
]]></programlisting>
|
|
|
|
</simplesect>
|
|
|
|
<simplesect>
|
|
<title>clearToken()</title>
|
|
|
|
<para>
|
|
Clear authentication state, including tokens. This can be useful if application has detected the session
|
|
has expired, for example if updating token fails. Invoking this results in onAuthLogout callback listener
|
|
being invoked.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[
|
|
keycloak.updateToken(5).error(function() {
|
|
keycloak.clearToken();
|
|
});
|
|
]]></programlisting>
|
|
|
|
</simplesect>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Callback Events</title>
|
|
|
|
<para>The adapter supports setting callback listeners for certain events. For example:
|
|
<programlisting><![CDATA[
|
|
keycloak.onAuthSuccess = function() { alert('authenticated'); }
|
|
]]></programlisting>
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>onReady(authenticated) - called when the adapter is initialized</listitem>
|
|
<listitem>onAuthSuccess - called when a user is successfully authenticated</listitem>
|
|
<listitem>onAuthError - called if there was an error during authentication</listitem>
|
|
<listitem>onAuthRefreshSuccess - called when the token is refreshed</listitem>
|
|
<listitem>onAuthRefreshError - called if there was an error while trying to refresh the token</listitem>
|
|
<listitem>onAuthLogout - called if the user is logged out (will only be called if the session status iframe is enabled, or in Cordova mode)</listitem>
|
|
<listitem>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</listitem>
|
|
</itemizedlist>
|
|
</section>
|
|
</section>
|
|
</section> |