keycloak-aplcache

Merge pull request #299 from stianst/master Added support

3/19/2014 2:02:05 PM

Details

diff --git a/examples/cordova/.gitignore b/examples/cordova/.gitignore
new file mode 100644
index 0000000..758f6ed
--- /dev/null
+++ b/examples/cordova/.gitignore
@@ -0,0 +1,4 @@
+platforms
+plugins
+www/keycloak.js
+www/keycloak.json
diff --git a/examples/cordova/example-realm.json b/examples/cordova/example-realm.json
new file mode 100755
index 0000000..37e899e
--- /dev/null
+++ b/examples/cordova/example-realm.json
@@ -0,0 +1,72 @@
+{
+    "realm": "example",
+    "enabled": true,
+    "sslNotRequired": true,
+    "registrationAllowed": true,
+    "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
+    "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+    "requiredCredentials": [ "password" ],
+    "users" : [
+        {
+            "username" : "user",
+            "enabled": true,
+            "email" : "sample-user@example",
+            "firstName": "Sample",
+            "lastName": "User",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ]
+        }
+    ],
+    "roles" : {
+        "realm" : [
+            {
+                "name": "user",
+                "description": "User privileges"
+            },
+            {
+                "name": "admin",
+                "description": "Administrator privileges"
+            }
+        ]
+    },
+    "roleMappings": [
+        {
+            "username": "user",
+            "roles": ["user"]
+        }
+    ],
+    "scopeMappings": [
+        {
+            "client": "cordova",
+            "roles": ["user"]
+        }
+    ],
+    "applications": [
+        {
+            "name": "cordova",
+            "enabled": true,
+            "publicClient": true,
+            "redirectUris": [
+                "http://localhost"
+            ]
+        }
+    ],
+    "applicationRoleMappings": {
+        "account": [
+            {
+                "username": "user",
+                "roles": ["view-profile", "manage-account"]
+            }
+        ]
+    },
+    "applicationScopeMappings": {
+        "account": [
+            {
+                "client": "cordova",
+                "roles": ["view-profile"]
+            }
+        ]
+    }
+}
diff --git a/examples/cordova/README.md b/examples/cordova/README.md
new file mode 100644
index 0000000..9173783
--- /dev/null
+++ b/examples/cordova/README.md
@@ -0,0 +1,31 @@
+Basic Cordova Example
+=====================
+
+Before running this example you need to have Cordova installed with a phone or emulator available.
+
+Start and configure Keycloak
+----------------------------
+
+Start Keycloak bound to an IP address available to the phone or emulator. For example:
+
+    bin/standalone.sh -b 192.168.0.10
+
+Open the Keycloak admin console, click on Add Realm, click on 'Choose a JSON file', selct example-realm.json and click Upload.
+
+Navigate to applications, click on 'Cordova', select 'Installation' and in the 'Format option' drop-down select 'keycloak.json'. Download this file to the www folder.
+
+Download '/js/keycloak.js' from the server to the www folder as well. For example:
+
+    wget http://192.168.0.10:8080/auth/js/keycloak.js
+
+
+Install to Android phone or emulator
+------------------------------------
+
+    mkdir platforms plugins
+    cordova plugin add org.apache.cordova.inappbrowser
+    cordova platform add android
+    cordova run android
+
+
+Once the application is opened you can login with username: 'user', and password: 'password'.
diff --git a/examples/cordova/www/config.xml b/examples/cordova/www/config.xml
new file mode 100644
index 0000000..c568b5a
--- /dev/null
+++ b/examples/cordova/www/config.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<widget xmlns="http://www.w3.org/ns/widgets" xmlns:gap="http://phonegap.com/ns/1.0" id="org.keycloak.examples.cordova" version="1.0.0">
+    <name>Keycloak Auth</name>
+    <description>Keycloak Cordova Example</description>
+    <author href="http://www.keycloak.org">Keycloak Team</author>
+
+    <feature name="http://api.phonegap.com/1.0/device" />
+
+    <preference name="permissions" value="none"/>
+
+    <gap:plugin name="org.apache.cordova.inappbrowser" />
+
+    <access origin="http://*"/>
+</widget>
diff --git a/examples/cordova/www/index.html b/examples/cordova/www/index.html
new file mode 100644
index 0000000..27d7db1
--- /dev/null
+++ b/examples/cordova/www/index.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Authentication Example</title>
+
+    <script type="text/javascript" charset="utf-8" src="cordova.js"></script>
+    <script type="text/javascript" charset="utf-8" src="keycloak.js"></script>
+    <script type="text/javascript" charset="utf-8">
+        var keycloak = new Keycloak();
+
+        keycloak.onReady = updateState;
+        keycloak.onAuthSuccess = updateState;
+        keycloak.onAuthRefreshSuccess = updateState;
+        keycloak.onAuthLogout = updateState;
+
+        function updateState() {
+            console.debug('Updating state');
+
+            if (keycloak.authenticated) {
+                document.getElementById('authenticated').style.display = 'block';
+                document.getElementById('not-authenticated').style.display = 'none';
+
+                document.getElementById('subject').innerText = keycloak.subject;
+                document.getElementById('username').innerText = keycloak.idToken.preferred_username;
+                document.getElementById('tokenExpires').innerText = new Date(keycloak.tokenParsed.exp * 1000).toLocaleString();
+                document.getElementById('tokenRefreshExpires').innerText = new Date(keycloak.refreshTokenParsed.exp * 1000).toLocaleString();
+            } else {
+                document.getElementById('authenticated').style.display = 'none';
+                document.getElementById('not-authenticated').style.display = 'block';
+            }
+        }
+
+        document.addEventListener("deviceready", function() {
+            console.debug('Device ready');
+
+            keycloak.init('check-sso');
+        }, false);
+    </script>
+</head>
+<body>
+<div id="authenticated" style="display: none;">
+    <div>
+        <button onclick="keycloak.logout()">Log out</button>
+        <button onclick="keycloak.updateToken()">Refresh token</button>
+        <button onclick="keycloak.accountManagement()">Manage account</button>
+    </div>
+    <div>
+        <table>
+            <tr>
+                <td>Subject</td>
+                <td id="subject"></td>
+            </tr>
+            <tr>
+                <td>Username</td>
+                <td id="username"></td>
+            </tr>
+            <tr>
+                <td>Token expires</td>
+                <td id="tokenExpires"></td>
+            </tr>
+            <tr>
+                <td>Refresh token expires</td>
+                <td id="tokenRefreshExpires"></td>
+            </tr>
+        </table>
+    </div>
+</div>
+<div id="not-authenticated" style="display: none;">
+    <div>
+        <button onclick="keycloak.login()">Log in</button>
+    </div>
+    <div>
+        <p>Not authenticated</p>
+    </div>
+</div>
+</body>
+</html>
+
diff --git a/examples/demo-template/customer-app-cli/pom.xml b/examples/demo-template/customer-app-cli/pom.xml
index 2abf670..363bc3e 100755
--- a/examples/demo-template/customer-app-cli/pom.xml
+++ b/examples/demo-template/customer-app-cli/pom.xml
@@ -14,14 +14,6 @@
     <name>Customer Portal CLI</name>
     <description/>
 
-    <repositories>
-        <repository>
-            <id>jboss</id>
-            <name>jboss repo</name>
-            <url>http://repository.jboss.org/nexus/content/groups/public/</url>
-        </repository>
-    </repositories>
-
     <dependencies>
         <dependency>
             <groupId>org.keycloak</groupId>
@@ -33,6 +25,14 @@
     <build>
         <plugins>
             <plugin>
+                <groupId>org.jboss.as.plugins</groupId>
+                <artifactId>jboss-as-maven-plugin</artifactId>
+                <version>7.4.Final</version>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-deploy-plugin</artifactId>
                 <configuration>
diff --git a/examples/demo-template/customer-app-js/src/main/webapp/customers/view.html b/examples/demo-template/customer-app-js/src/main/webapp/customers/view.html
index 845a1ff..445876f 100755
--- a/examples/demo-template/customer-app-js/src/main/webapp/customers/view.html
+++ b/examples/demo-template/customer-app-js/src/main/webapp/customers/view.html
@@ -5,7 +5,8 @@
 </head>
 <body bgcolor="#E3F6CE">
 
-<p>Goto: <a href="#" onclick="keycloak.logout()">logout</a></p>
+<p>Goto: <a href="http://localhost:8080/product-portal">products</a> | <a href="#" onclick="keycloak.logout()">logout</a> | <a href="#" onclick="keycloak.accountManagement()">manage acct</a></p>
+
 User <b id="subject"></b> made this request.
 <p><b>User details (from <span id="profileType"></span>)</b></p>
 <p>Username: <span id="username"></span></p>
@@ -18,16 +19,11 @@ User <b id="subject"></b> made this request.
 <div id="customers"></div>
 
 <script>
-    var keycloak = Keycloak({
-        clientId: 'customer-portal-js',
-        realm: 'demo',
-        onload: 'login-required'
-    });
+    var keycloak = Keycloak('../keycloak.json');
 
     var loadData = function () {
         document.getElementById('subject').innerText = keycloak.subject;
 
-        console.debug(keycloak.idToken);
         if (keycloak.idToken) {
             document.getElementById('profileType').innerText = 'IDToken';
             document.getElementById('username').innerText = keycloak.idToken.preferred_username;
@@ -74,14 +70,14 @@ User <b id="subject"></b> made this request.
 
     var loadFailure = function () {
         document.getElementById('customers').innerHTML = '<b>Failed to load data.  Check console log</b>';
-
     };
 
     var reloadData = function () {
-        keycloak.onValidAccessToken(loadData, loadFailure);
+        keycloak.updateToken(10).success(loadData).error(loadFailure);
     }
 
-    keycloak.init(loadData, loadFailure);
+    keycloak.onAuthSuccess = loadData;
+    keycloak.init('login-required');
 
 </script>
 
diff --git a/examples/demo-template/customer-app-js/src/main/webapp/keycloak.json b/examples/demo-template/customer-app-js/src/main/webapp/keycloak.json
new file mode 100644
index 0000000..d73332e
--- /dev/null
+++ b/examples/demo-template/customer-app-js/src/main/webapp/keycloak.json
@@ -0,0 +1,8 @@
+{
+  "realm" : "demo",
+  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+  "auth-server-url" : "http://localhost:8080/auth",
+  "ssl-not-required" : true,
+  "resource" : "customer-portal-js",
+  "public-client" : true
+}
\ No newline at end of file
diff --git a/examples/demo-template/testrealm.json b/examples/demo-template/testrealm.json
index a82e9e8..81af756 100755
--- a/examples/demo-template/testrealm.json
+++ b/examples/demo-template/testrealm.json
@@ -52,6 +52,10 @@
             "roles": ["user"]
         },
         {
+            "client": "customer-portal-js",
+            "roles": ["user"]
+        },
+        {
             "client": "product-portal",
             "roles": ["user"]
         }
diff --git a/examples/js-console/README.md b/examples/js-console/README.md
new file mode 100644
index 0000000..749c0f3
--- /dev/null
+++ b/examples/js-console/README.md
@@ -0,0 +1,17 @@
+Basic JavaScript Example
+========================
+
+Start and configure Keycloak
+----------------------------
+
+Start Keycloak:
+
+    bin/standalone.sh
+
+Open the Keycloak admin console, click on Add Realm, click on 'Choose a JSON file', selct example-realm.json and click Upload.
+
+Deploy the JS Console to Keycloak by running:
+
+    mvn install jboss-as:deploy
+
+Open the console at http://localhost:8080/js-console and login with username: 'user', and password: 'password'.
diff --git a/examples/js-console/src/main/webapp/index.html b/examples/js-console/src/main/webapp/index.html
index 58513ad..3b9e4a0 100644
--- a/examples/js-console/src/main/webapp/index.html
+++ b/examples/js-console/src/main/webapp/index.html
@@ -1,6 +1,6 @@
 <html>
 <head>
-    <script src="http://localhost:8080/auth/js/keycloak.js"></script>
+    <script src="/auth/js/keycloak.js"></script>
 </head>
 <body>
 
@@ -36,7 +36,7 @@
     }
 
     function refreshToken(minValidity) {
-        keycloak.refreshAccessToken(minValidity).success(function(refreshed) {
+        keycloak.updateToken(minValidity).success(function(refreshed) {
             if (refreshed) {
                 output(keycloak.tokenParsed);
             } else {
diff --git a/forms/account-api/src/main/java/org/keycloak/account/Account.java b/forms/account-api/src/main/java/org/keycloak/account/Account.java
index 61e92e2..060b8a5 100644
--- a/forms/account-api/src/main/java/org/keycloak/account/Account.java
+++ b/forms/account-api/src/main/java/org/keycloak/account/Account.java
@@ -25,6 +25,6 @@ public interface Account {
 
     public Account setRealm(RealmModel realm);
 
-    public Account setReferrer(String referrer);
+    public Account setReferrer(String[] referrer);
 
 }
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccount.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccount.java
index 1a5cc67..143aa1c 100644
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccount.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccount.java
@@ -36,7 +36,7 @@ public class FreeMarkerAccount implements Account {
     private UserModel user;
     private Response.Status status = Response.Status.OK;
     private RealmModel realm;
-    private String referrer;
+    private String[] referrer;
 
     public static enum MessageType {SUCCESS, WARNING, ERROR}
 
@@ -82,9 +82,8 @@ public class FreeMarkerAccount implements Account {
             attributes.put("message", new MessageBean(messages.containsKey(message) ? messages.getProperty(message) : message, messageType));
         }
 
-        ApplicationModel referrerApp = getReferrer();
-        if (referrerApp != null) {
-            attributes.put("referrer", new ReferrerBean(referrerApp));
+        if (referrer != null) {
+            attributes.put("referrer", new ReferrerBean(referrer));
         }
 
         attributes.put("url", new UrlBean(realm, theme, baseUri));
@@ -114,16 +113,6 @@ public class FreeMarkerAccount implements Account {
         }
     }
 
-    private ApplicationModel getReferrer() {
-        if (referrer != null) {
-            ApplicationModel app = realm.getApplicationByName(referrer);
-            if (app != null) {
-                return app;
-            }
-        }
-        return null;
-    }
-
     @Override
     public Account setError(String message) {
         this.message = message;
@@ -164,7 +153,7 @@ public class FreeMarkerAccount implements Account {
     }
 
     @Override
-    public Account setReferrer(String referrer) {
+    public Account setReferrer(String[] referrer) {
         this.referrer = referrer;
         return this;
     }
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/ReferrerBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/ReferrerBean.java
index 8904bf6..0734a7b 100644
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/ReferrerBean.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/ReferrerBean.java
@@ -1,24 +1,22 @@
 package org.keycloak.account.freemarker.model;
 
-import org.keycloak.models.ApplicationModel;
-
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
 public class ReferrerBean {
 
-    private ApplicationModel referrer;
+    private String[] referrer;
 
-    public ReferrerBean(ApplicationModel referrer) {
+    public ReferrerBean(String[] referrer) {
         this.referrer = referrer;
     }
 
     public String getName() {
-        return referrer.getName();
+        return referrer[0];
     }
 
-    public String getBaseUrl() {
-        return referrer.getBaseUrl();
+    public String getUrl() {
+        return referrer[1];
     }
 
 }
diff --git a/forms/common-themes/src/main/resources/theme/account/base/template.ftl b/forms/common-themes/src/main/resources/theme/account/base/template.ftl
index 376a421..d6a2f5e 100644
--- a/forms/common-themes/src/main/resources/theme/account/base/template.ftl
+++ b/forms/common-themes/src/main/resources/theme/account/base/template.ftl
@@ -28,7 +28,7 @@
             <div class="navbar-collapse navbar-collapse-1">
                 <div class="container">
                     <ul class="nav navbar-nav navbar-utility">
-                        <#if referrer?has_content && referrer.baseUrl?has_content><li><a href="${referrer.baseUrl}">Back to ${referrer.name}</a></li></#if>
+                        <#if referrer?has_content && referrer.url?has_content><li><a href="${referrer.url}" id="referrer">Back to ${referrer.name}</a></li></#if>
                         <li><a href="${url.logoutUrl}">Sign Out</a></li>
                     </ul>
                 </div>
diff --git a/integration/js/src/main/resources/META-INF/resources/js/keycloak.js b/integration/js/src/main/resources/META-INF/resources/js/keycloak.js
index 6860b78..11da6c8 100755
--- a/integration/js/src/main/resources/META-INF/resources/js/keycloak.js
+++ b/integration/js/src/main/resources/META-INF/resources/js/keycloak.js
@@ -4,107 +4,118 @@ var Keycloak = function (config) {
     }
 
     var kc = this;
-    kc.authenticated = false;
-
-    var configPromise = createPromise();
-    configPromise.name = 'config';
-
-    if (!config) {
-        loadConfig('keycloak.json', configPromise);
-    } else if (typeof config === 'string') {
-        loadConfig(config, configPromise);
-    } else {
-        if (!config['url']) {
-            var scripts = document.getElementsByTagName('script');
-            for (var i = 0; i < scripts.length; i++) {
-                if (scripts[i].src.match(/.*keycloak\.js/)) {
-                    config.url = scripts[i].src.substr(0, scripts[i].src.indexOf('/js/keycloak.js'));
-                    break;
-                }
-            }
-        }
+    var adapter;
 
-        if (!config.realm) {
-            throw 'realm missing';
-        }
+    kc.init = function (init) {
+        kc.authenticated = false;
 
-        if (!config.clientId) {
-            throw 'clientId missing';
+        if (window.Cordova) {
+            adapter = loadAdapter('cordova');
+        } else {
+            adapter = loadAdapter();
         }
 
-        kc.authServerUrl = config.url;
-        kc.realm = config.realm;
-        kc.clientId = config.clientId;
+        var promise = createPromise();
 
-        configPromise.setSuccess();
-    }
+        var initPromise = createPromise();
+        initPromise.promise.success(function() {
+            kc.onReady && kc.onReady(kc.authenticated);
+            promise.setSuccess(kc.authenticated);
+        }).error(promise.error);
 
-    kc.init = function (init) {
-        var promise = createPromise();
-        var callback = parseCallback(window.location.href);
+        var configPromise = loadConfig(config);
 
         function processInit() {
+            var callback = parseCallback(window.location.href);
             if (callback) {
                 window.history.replaceState({}, null, location.protocol + '//' + location.host + location.pathname + (callback.fragment ? '#' + callback.fragment : ''));
-                processCallback(callback, promise);
+                processCallback(callback, initPromise);
                 return;
             } else if (init) {
                 if (init.code || init.error) {
-                    processCallback(init, promise);
+                    processCallback(init, initPromise);
                     return;
                 } else if (init.token || init.refreshToken) {
                     setToken(init.token, init.refreshToken);
+                    initPromise.setSuccess();
                 } else if (init == 'login-required') {
-                    kc.login();
-                    return;
+                    var p = kc.login();
+                    if (p) {
+                        p.success(function() {
+                            initPromise.setSuccess();
+                        }).error(function() {
+                            initPromise.setError();
+                        });
+                    };
                 } else if (init == 'check-sso') {
-                    window.location = kc.createLoginUrl() + '&prompt=none';
-                    return;
+                    var p = kc.login({ prompt: 'none' });
+                    if (p) {
+                        p.success(function() {
+                            initPromise.setSuccess();
+                        }).error(function() {
+                            initPromise.setSuccess();
+                        });
+                    };
+                } else {
+                    throw 'invalid init: ' + init;
                 }
+            } else {
+                initPromise.setSuccess();
             }
-
-            promise.setSuccess(false);
         }
 
-        configPromise.promise.success(processInit);
+        configPromise.success(processInit);
 
         return promise.promise;
     }
 
-    kc.login = function (redirectUri) {
-        window.location.href = kc.createLoginUrl(redirectUri);
+    kc.login = function (options) {
+        return adapter.login(options);
     }
 
-    kc.createLoginUrl = function(redirectUri) {
+    kc.createLoginUrl = function(options) {
         var state = createUUID();
 
         sessionStorage.oauthState = state;
         var url = getRealmUrl()
             + '/tokens/login'
             + '?client_id=' + encodeURIComponent(kc.clientId)
-            + '&redirect_uri=' + getEncodedRedirectUri(redirectUri)
+            + '&redirect_uri=' + encodeURIComponent(adapter.redirectUri(options))
             + '&state=' + encodeURIComponent(state)
             + '&response_type=code';
 
+        if (options && options.prompt) {
+            url += '&prompt=' + options.prompt;
+        }
+
         return url;
     }
 
-    kc.logout = function(redirectUri) {
-        setToken(null, null);
-        window.location.href = kc.createLogoutUrl(redirectUri);
+    kc.logout = function(options) {
+        return adapter.logout(options);
     }
 
-    kc.clearToken = function() {
-        setToken(null, null);
+    kc.createLogoutUrl = function(options) {
+        var url = getRealmUrl()
+            + '/tokens/logout'
+            + '?redirect_uri=' + encodeURIComponent(adapter.redirectUri(options));
+
+        return url;
     }
 
-    kc.createLogoutUrl = function(redirectUri) {
+    kc.createAccountUrl = function(options) {
         var url = getRealmUrl()
-            + '/tokens/logout'
-            + '?redirect_uri=' + getEncodedRedirectUri(redirectUri);
+            + '/account'
+            + '?referrer=' + kc.clientId
+            + '&referrer_uri=' + encodeURIComponent(adapter.redirectUri(options));
+
         return url;
     }
 
+    kc.accountManagement = function() {
+        return adapter.accountManagement();
+    }
+
     kc.hasRealmRole = function (role) {
         var access = kc.realmAccess;
         return access && access.roles.indexOf(role) >= 0 || false;
@@ -144,7 +155,20 @@ var Keycloak = function (config) {
         return promise.promise;
     }
 
-    kc.refreshAccessToken = function(minValidity) {
+    kc.isTokenExpired = function(minValidity) {
+        if (!kc.tokenParsed || !kc.refreshToken) {
+            throw 'Not authenticated';
+        }
+
+        var expiresIn = kc.tokenParsed['exp'] - (new Date().getTime() / 1000);
+        if (minValidity) {
+            expiresIn -= minValidity;
+        }
+
+        return expiresIn < 0;
+    }
+
+    kc.updateToken = function(minValidity) {
         if (!kc.tokenParsed || !kc.refreshToken) {
             throw 'Not authenticated';
         }
@@ -152,8 +176,7 @@ var Keycloak = function (config) {
         var promise = createPromise();
 
         if (minValidity) {
-            var expiresIn = kc.tokenParsed['exp'] - (new Date().getTime() / 1000);
-            if (expiresIn > minValidity) {
+            if (!kc.isTokenExpired(minValidity)) {
                 promise.setSuccess(false);
                 return promise.promise;
             }
@@ -191,15 +214,6 @@ var Keycloak = function (config) {
         return promise.promise;
     }
 
-    kc.processCallback = function(url) {
-        var callback = parseCallback(url);
-        if (callback) {
-            var promise = createPromise();
-            processCallback(callback, promise);
-            return promise;
-        }
-    }
-
     function getRealmUrl() {
         return kc.authServerUrl + '/rest/realms/' + encodeURIComponent(kc.realm);
     }
@@ -231,10 +245,10 @@ var Keycloak = function (config) {
                         var tokenResponse = JSON.parse(req.responseText);
                         setToken(tokenResponse['access_token'], tokenResponse['refresh_token']);
                         kc.onAuthSuccess && kc.onAuthSuccess();
-                        promise.setSuccess(true);
+                        promise && promise.setSuccess();
                     } else {
                         kc.onAuthError && kc.onAuthError();
-                        promise.setError();
+                        promise && promise.setError();
                     }
                 }
             };
@@ -243,33 +257,75 @@ var Keycloak = function (config) {
         } else if (error) {
             if (prompt != 'none') {
                 kc.onAuthError && kc.onAuthError();
-                promise.setError();
+                promise && promise.setError();
             }
         }
     }
 
-    function loadConfig(url, configPromise) {
-        var req = new XMLHttpRequest();
-        req.open('GET', url, true);
-        req.setRequestHeader('Accept', 'application/json');
+    function loadConfig(url) {
+        var promise = createPromise();
+        var configUrl;
 
-        req.onreadystatechange = function () {
-            if (req.readyState == 4) {
-                if (req.status == 200) {
-                    var config = JSON.parse(req.responseText);
+        if (!config) {
+            configUrl = 'keycloak.json';
+        } else if (typeof config === 'string') {
+            configUrl = config;
+        }
+
+        if (configUrl) {
+            var req = new XMLHttpRequest();
+            req.open('GET', configUrl, true);
+            req.setRequestHeader('Accept', 'application/json');
 
-                    kc.authServerUrl = config['auth-server-url'];
-                    kc.realm = config['realm'];
-                    kc.clientId = config['resource'];
+            req.onreadystatechange = function () {
+                if (req.readyState == 4) {
+                    if (req.status == 200) {
+                        var config = JSON.parse(req.responseText);
 
-                    configPromise.setSuccess();
-                } else {
-                    configPromise.setError();
+                        kc.authServerUrl = config['auth-server-url'];
+                        kc.realm = config['realm'];
+                        kc.clientId = config['resource'];
+
+                        promise.setSuccess();
+                    } else {
+                        promise.setError();
+                    }
+                }
+            };
+
+            req.send();
+        } else {
+            if (!config['url']) {
+                var scripts = document.getElementsByTagName('script');
+                for (var i = 0; i < scripts.length; i++) {
+                    if (scripts[i].src.match(/.*keycloak\.js/)) {
+                        config.url = scripts[i].src.substr(0, scripts[i].src.indexOf('/js/keycloak.js'));
+                        break;
+                    }
                 }
             }
-        };
 
-        req.send();
+            if (!config.realm) {
+                throw 'realm missing';
+            }
+
+            if (!config.clientId) {
+                throw 'clientId missing';
+            }
+
+            kc.authServerUrl = config.url;
+            kc.realm = config.realm;
+            kc.clientId = config.clientId;
+
+            promise.setSuccess();
+        }
+
+        return promise.promise;
+    }
+
+    function clearToken() {
+        setToken(null, null);
+        kc.onAuthLogout && kc.onAuthLogout();
     }
 
     function setToken(token, refreshToken) {
@@ -310,21 +366,6 @@ var Keycloak = function (config) {
         }
     }
 
-    function getEncodedRedirectUri(redirectUri) {
-        var url;
-        if (redirectUri) {
-            url = redirectUri;
-        } else if (kc.redirectUri) {
-            url = kc.redirectUri;
-        } else {
-            url = (location.protocol + '//' + location.hostname + (location.port && (':' + location.port)) + location.pathname);
-            if (location.hash) {
-                url += '?redirect_fragment=' + encodeURIComponent(location.hash.substring(1));
-            }
-        }
-        return encodeURI(url);
-    }
-
     function createUUID() {
         var s = [];
         var hexDigits = '0123456789abcdef';
@@ -339,7 +380,6 @@ var Keycloak = function (config) {
     }
 
     function parseCallback(url) {
-
         if (url.indexOf('?') != -1) {
             var oauth = {};
 
@@ -365,7 +405,7 @@ var Keycloak = function (config) {
                 }
             }
 
-            if (oauth.state && oauth.state == sessionStorage.oauthState) {
+            if ((oauth.code || oauth.error) && oauth.state && oauth.state == sessionStorage.oauthState) {
                 delete sessionStorage.oauthState;
                 return oauth;
             }
@@ -412,6 +452,103 @@ var Keycloak = function (config) {
         return p;
     }
 
+    function loadAdapter(type) {
+        if (!type || type == 'default') {
+            return {
+                login: function(options) {
+                    window.location.href = kc.createLoginUrl(options);
+                },
+
+                logout: function(options) {
+                    window.location.href = kc.createLogoutUrl(options);
+                },
+
+                accountManagement : function() {
+                    window.location.href = kc.createAccountUrl();
+                },
+
+                redirectUri: function(options) {
+                    if (options && options.redirectUri) {
+                        return options.redirectUri;
+                    } else if (kc.redirectUri) {
+                        return kc.redirectUri;
+                    } else {
+                        var url = (location.protocol + '//' + location.hostname + (location.port && (':' + location.port)) + location.pathname);
+                        if (location.hash) {
+                            url += '?redirect_fragment=' + encodeURIComponent(location.hash.substring(1));
+                        }
+                        return url;
+                    }
+                }
+            };
+        }
+
+        if (type == 'cordova') {
+            console.debug('Enabling Cordova support');
+
+            return {
+                login: function(options) {
+                    var promise = createPromise();
+
+                    var o = 'location=no';
+                    if (options && options.prompt == 'none') {
+                        o += ',hidden=yes';
+                    }
+
+                    var loginUrl = kc.createLoginUrl(options);
+                    var ref = window.open(loginUrl, '_blank', o);
+                    ref.addEventListener('loadstart', function(event) {
+                        if (event.url.indexOf('http://localhost') == 0) {
+                            var callback = parseCallback(event.url);
+                            ref.close();
+                            processCallback(callback);
+
+                            if (callback.code) {
+                                promise.setSuccess();
+                            } else {
+                                promise.setError();
+                            }
+                        }
+                    });
+
+                    return promise.promise;
+                },
+
+                logout: function(options) {
+                    var promise = createPromise();
+
+                    var logoutUrl = kc.createLogoutUrl(options);
+                    var ref = window.open(logoutUrl, '_blank', 'location=no,hidden=yes');
+                    ref.addEventListener('loadstart', function(event) {
+                        if (event.url.indexOf('http://localhost') == 0) {
+                            ref.close();
+                            clearToken();
+                            promise.setSuccess();
+                        }
+                    });
+
+                    return promise.promise;
+                },
+
+                accountManagement : function() {
+                    var accountUrl = kc.createAccountUrl();
+                    var ref = window.open(accountUrl, '_blank', 'location=no');
+                    ref.addEventListener('loadstart', function(event) {
+                        if (event.url.indexOf('http://localhost') == 0) {
+                            ref.close();
+                        }
+                    });
+                },
+
+                redirectUri: function(options) {
+                    return 'http://localhost';
+                }
+            }
+        }
+
+        throw 'invalid adapter type: ' + type;
+    }
+
     var idTokenProperties = [
         "name", 
         "given_name", 
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 593f3d3..eb53402 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -99,7 +99,7 @@ public class AccountService {
 
             Account account = AccountLoader.load().createAccount(uriInfo).setRealm(realm).setUser(auth.getUser());
 
-            String referrer = getReferrer();
+            String[] referrer = getReferrer();
             if (referrer != null) {
                 account.setReferrer(referrer);
             }
@@ -377,13 +377,17 @@ public class AccountService {
             uriBuilder.queryParam("path", path);
         }
 
-        String referrer = getReferrer();
+        String referrer = uriInfo.getQueryParameters().getFirst("referrer");
         if (referrer != null) {
             uriBuilder.queryParam("referrer", referrer);
         }
 
-        URI accountUri = uriBuilder.build(realm.getName());
+        String referrerUri = uriInfo.getQueryParameters().getFirst("referrer_uri");
+        if (referrerUri != null) {
+            uriBuilder.queryParam("referrer_uri", referrerUri);
+        }
 
+        URI accountUri = uriBuilder.build(realm.getName());
 
         oauth.setStateCookiePath(accountUri.getRawPath());
         return oauth.redirect(uriInfo, accountUri.toString());
@@ -397,20 +401,34 @@ public class AccountService {
         return auth;
     }
 
-    private String getReferrer() {
+    private String[] getReferrer() {
         String referrer = uriInfo.getQueryParameters().getFirst("referrer");
-        if (referrer != null) {
-            return referrer;
+        if (referrer == null) {
+            return null;
         }
 
-        String referrerUrl = headers.getHeaderString("Referer");
-        if (referrerUrl != null) {
-            for (ApplicationModel a : realm.getApplications()) {
-                if (a.getBaseUrl() != null && referrerUrl.startsWith(a.getBaseUrl())) {
-                    return a.getName();
+        String referrerUri = uriInfo.getQueryParameters().getFirst("referrer_uri");
+
+        ApplicationModel application = realm.getApplicationByName(referrer);
+        if (application != null) {
+            if (referrerUri != null) {
+                referrerUri = TokenService.verifyRedirectUri(referrerUri, application);
+            } else {
+                referrerUri = application.getBaseUrl();
+            }
+
+            if (referrerUri != null) {
+                return new String[] { referrer, referrerUri };
+            }
+        } else if (referrerUri != null) {
+            ClientModel client = realm.getOAuthClient(referrer);
+            if (client != null) {
+                referrerUri = TokenService.verifyRedirectUri(referrerUri, application);
+
+                if (referrerUri != null) {
+                    return new String[] { referrer, referrerUri };
                 }
             }
-            return null;
         }
 
         return null;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java
index 0aa9075..27e013b 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java
@@ -107,26 +107,21 @@ public class AccountTest {
         });
     }
 
-    //@Test
-    public void returnToAppFromHeader() {
-        appPage.open();
-        appPage.openAccount();
+    @Test
+    public void returnToAppFromQueryParam() {
+        driver.navigate().to(AccountUpdateProfilePage.PATH + "?referrer=test-app");
         loginPage.login("test-user@localhost", "password");
-
         Assert.assertTrue(profilePage.isCurrent());
         profilePage.backToApplication();
 
         Assert.assertTrue(appPage.isCurrent());
-    }
 
-    //@Test
-    public void returnToAppFromQueryParam() {
-        driver.navigate().to(AccountUpdateProfilePage.PATH + "?referrer=test-app");
-        loginPage.login("test-user@localhost", "password");
+        driver.navigate().to(AccountUpdateProfilePage.PATH + "?referrer=test-app&referrer_uri=http://localhost:8081/app?test");
         Assert.assertTrue(profilePage.isCurrent());
         profilePage.backToApplication();
 
         Assert.assertTrue(appPage.isCurrent());
+        Assert.assertEquals(appPage.baseUrl + "?test", driver.getCurrentUrl());
     }
 
     @Test
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
index 3f9c784..582c112 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
@@ -42,7 +42,7 @@ public class AccountUpdateProfilePage extends AbstractAccountPage {
     private WebElement emailInput;
 
 
-    @FindBy(linkText = "Back to application")
+    @FindBy(id = "referrer")
     private WebElement backToApplicationLink;
 
     @FindBy(css = "button[type=\"submit\"]")
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AppPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AppPage.java
index c0ce139..7fa6c9e 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AppPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AppPage.java
@@ -30,7 +30,7 @@ import org.openqa.selenium.support.FindBy;
  */
 public class AppPage extends AbstractPage {
 
-    private String baseUrl = "http://localhost:8081/app";
+    public static final String baseUrl = "http://localhost:8081/app";
 
     @FindBy(id = "account")
     private WebElement accountLink;