keycloak-aplcache

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..43bd527
--- /dev/null
+++ b/examples/cordova/www/index.html
@@ -0,0 +1,77 @@
+<!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>
+    </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/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..821d0e1 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,117 @@ 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() {
         var url = getRealmUrl()
-            + '/tokens/logout'
-            + '?redirect_uri=' + getEncodedRedirectUri(redirectUri);
+            + '/account'
+            + '?referrer=' + kc.clientId;
+
         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 +154,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 +175,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 +213,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 +244,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 +256,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 +365,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 +379,6 @@ var Keycloak = function (config) {
     }
 
     function parseCallback(url) {
-
         if (url.indexOf('?') != -1) {
             var oauth = {};
 
@@ -365,7 +404,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 +451,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",