keycloak-developers

KEYCLOAK-2606: add support for native browsers on cordova KEYCLOAK-2606

6/9/2018 6:26:16 PM

Details

diff --git a/adapters/oidc/js/src/main/resources/keycloak.d.ts b/adapters/oidc/js/src/main/resources/keycloak.d.ts
index 51d3191..eed2bf4 100644
--- a/adapters/oidc/js/src/main/resources/keycloak.d.ts
+++ b/adapters/oidc/js/src/main/resources/keycloak.d.ts
@@ -29,7 +29,7 @@ export = Keycloak;
 declare function Keycloak(config?: string|{}): Keycloak.KeycloakInstance;
 
 declare namespace Keycloak {
-	type KeycloakAdapterName = 'cordova'|'default' | any;
+	type KeycloakAdapterName = 'cordova' | 'cordova-native' |'default' | any;
 	type KeycloakOnLoad = 'login-required'|'check-sso';
 	type KeycloakResponseMode = 'query'|'fragment';
 	type KeycloakResponseType = 'code'|'id_token token'|'code id_token token';
@@ -99,6 +99,12 @@ declare namespace Keycloak {
 		responseMode?: KeycloakResponseMode;
 
 		/**
+		 * Specifies a default uri to redirect to after login or logout.
+		 * This is currently supported for adapter 'cordova-native' and 'default'
+		 */
+		redirectUri?: string;
+
+		/**
 		 * Set the OpenID Connect flow.
 		 * @default standard
 		 */
diff --git a/adapters/oidc/js/src/main/resources/keycloak.js b/adapters/oidc/js/src/main/resources/keycloak.js
index 0edde89..8b147bf 100755
--- a/adapters/oidc/js/src/main/resources/keycloak.js
+++ b/adapters/oidc/js/src/main/resources/keycloak.js
@@ -46,11 +46,10 @@
             kc.authenticated = false;
 
             callbackStorage = createCallbackStorage();
+            var adapters = ['default', 'cordova', 'cordova-native'];
 
-            if (initOptions && initOptions.adapter === 'cordova') {
-                adapter = loadAdapter('cordova');
-            } else if (initOptions && initOptions.adapter === 'default') {
-                adapter = loadAdapter();
+            if (initOptions && adapters.indexOf(initOptions.adapter) > -1) {
+                adapter = loadAdapter(initOptions.adapter);
             } else if (initOptions && typeof initOptions.adapter === "object") {
                 adapter = initOptions.adapter;
             } else {
@@ -106,6 +105,10 @@
                 if (initOptions.timeSkew != null) {
                     kc.timeSkew = initOptions.timeSkew;
                 }
+
+                if(initOptions.redirectUri) {
+                    kc.redirectUri = initOptions.redirectUri;
+                }
             }
 
             if (!kc.responseMode) {
@@ -1339,6 +1342,75 @@
                 }
             }
 
+            if (type == 'cordova-native') {
+                loginIframe.enable = false;
+
+                return {
+                    login: function(options) {
+                        var promise = createPromise();
+                        var loginUrl = kc.createLoginUrl(options);
+
+                        universalLinks.subscribe('keycloak', function(event) {
+                            universalLinks.unsubscribe('keycloak');
+                            window.cordova.plugins.browsertab.close();
+                            var oauth = parseCallback(event.url);
+                            processCallback(oauth, promise);
+                        });
+
+                        window.cordova.plugins.browsertab.openUrl(loginUrl);
+                        return promise.promise;
+                    },
+
+                    logout: function(options) {
+                        var promise = createPromise();
+                        var logoutUrl = kc.createLogoutUrl(options);
+
+                        universalLinks.subscribe('keycloak', function(event) {
+                            universalLinks.unsubscribe('keycloak');
+                            window.cordova.plugins.browsertab.close();
+                            kc.clearToken();
+                            promise.setSuccess();
+                        });
+
+                        window.cordova.plugins.browsertab.openUrl(logoutUrl);
+                        return promise.promise;
+                    },
+
+                    register : function(options) {
+                        var promise = createPromise();
+                        var registerUrl = kc.createRegisterUrl(options);
+                        universalLinks.subscribe('keycloak' , function(event) {
+                            universalLinks.unsubscribe('keycloak');
+                            window.cordova.plugins.browsertab.close();
+                            var oauth = parseCallback(event.url);
+                            processCallback(oauth, promise);
+                        });
+                        window.cordova.plugins.browsertab.openUrl(registerUrl);
+                        return promise.promise;
+
+                    },
+
+                    accountManagement : function() {
+                        var accountUrl = kc.createAccountUrl();
+                        if (typeof accountUrl !== 'undefined') {
+                            window.cordova.plugins.browsertab.openUrl(accountUrl);
+                        } else {
+                            throw "Not supported by the OIDC server";
+                        }
+                    },
+
+                    redirectUri: function(options) {
+                        if (options && options.redirectUri) {
+                            return options.redirectUri;
+                        } else if (kc.redirectUri) {
+                            return kc.redirectUri;
+                        } else {
+                            return "http://localhost";
+                        }
+                    }
+                }
+            }
+
             throw 'invalid adapter type: ' + type;
         }
 
diff --git a/examples/cordova-native/.gitignore b/examples/cordova-native/.gitignore
new file mode 100644
index 0000000..3ac935a
--- /dev/null
+++ b/examples/cordova-native/.gitignore
@@ -0,0 +1,6 @@
+platforms
+plugins
+www/keycloak.js
+www/keycloak.json
+node_modules
+ul_web_hooks
diff --git a/examples/cordova-native/config.xml b/examples/cordova-native/config.xml
new file mode 100644
index 0000000..0a0d962
--- /dev/null
+++ b/examples/cordova-native/config.xml
@@ -0,0 +1,25 @@
+<?xml version='1.0' encoding='utf-8'?>
+<widget id="org.keycloak.examples.cordova" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:gap="http://phonegap.com/ns/1.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" />
+    <preference name="AndroidLaunchMode" value="singleInstance" />
+    <gap:plugin name="cordova-plugin-inappbrowser" />
+    <gap:plugin name="cordova-plugin-whitelist" source="npm" version="1.0.0" />
+    <access origin="*" />
+    <allow-navigation href="*" />
+    <allow-intent href="http://*/*" />
+    <allow-intent href="https://*/*" />
+    <preference name="AndroidLaunchMode" value="singleTask" />
+    <universal-links>
+        <host name="keycloak-cordova-example.github.io" scheme="https">
+            <path event="keycloak" url="/login" />
+        </host>
+    </universal-links>
+    <plugin name="cordova-plugin-browsertab" spec="~0.2.0" />
+    <plugin name="cordova-plugin-deeplinks" spec="^1.1.0" />
+    <engine name="ios" spec="^4.5.4" />
+    <engine name="android" spec="^6.4.0" />
+</widget>
diff --git a/examples/cordova-native/example-realm.json b/examples/cordova-native/example-realm.json
new file mode 100755
index 0000000..131ccbd
--- /dev/null
+++ b/examples/cordova-native/example-realm.json
@@ -0,0 +1,61 @@
+{
+    "realm": "example",
+    "enabled": true,
+    "sslRequired": "external",
+    "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" }
+            ],
+            "realmRoles": [ "user" ],
+            "clientRoles": {
+                "account": ["view-profile", "manage-account"]
+            }
+        }
+    ],
+    "roles" : {
+        "realm" : [
+            {
+                "name": "user",
+                "description": "User privileges"
+            },
+            {
+                "name": "admin",
+                "description": "Administrator privileges"
+            }
+        ]
+    },
+    "scopeMappings": [
+        {
+            "client": "cordova",
+            "roles": ["user"]
+        }
+    ],
+    "clients": [
+        {
+            "clientId": "cordova",
+            "enabled": true,
+            "publicClient": true,
+            "redirectUris": ["android-app://org.keycloak.examples.cordova/https/keycloak-cordova-example.github.io/login"],
+            "webOrigins": ["localhost"]
+        }
+    ],
+    "clientScopeMappings": {
+        "account": [
+            {
+                "client": "cordova",
+                "roles": ["view-profile"]
+            }
+        ]
+    }
+}
diff --git a/examples/cordova-native/package.json b/examples/cordova-native/package.json
new file mode 100644
index 0000000..796ff56
--- /dev/null
+++ b/examples/cordova-native/package.json
@@ -0,0 +1,24 @@
+{
+  "name": "org.keycloak.examples.cordova",
+  "version": "1.0.0",
+  "displayName": "Keycloak Auth",
+  "cordova": {
+    "platforms": [
+      "ios",
+      "android"
+    ],
+    "plugins": {
+      "cordova-plugin-browsertab": {},
+      "cordova-plugin-deeplinks": {},
+      "cordova-plugin-whitelist": {}
+    }
+  },
+  "dependencies": {
+    "cordova-android": "^6.4.0",
+    "cordova-ios": "^4.5.4",
+    "cordova-plugin-browsertab": "^0.2.0",
+    "cordova-plugin-compat": "^1.2.0",
+    "cordova-plugin-deeplinks": "^1.1.0",
+    "cordova-plugin-whitelist": "^1.3.3"
+  }
+}
\ No newline at end of file
diff --git a/examples/cordova-native/package-lock.json b/examples/cordova-native/package-lock.json
new file mode 100644
index 0000000..63bb613
--- /dev/null
+++ b/examples/cordova-native/package-lock.json
@@ -0,0 +1,383 @@
+{
+  "name": "org.keycloak.examples.cordova",
+  "version": "1.0.0",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "balanced-match": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+    },
+    "base64-js": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz",
+      "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw=="
+    },
+    "brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "requires": {
+        "balanced-match": "1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+    },
+    "cordova-android": {
+      "version": "6.4.0",
+      "resolved": "https://registry.npmjs.org/cordova-android/-/cordova-android-6.4.0.tgz",
+      "integrity": "sha1-VK6NpXKKjX5e/MYXLT3MoXvH/n0=",
+      "requires": {
+        "android-versions": "1.2.1",
+        "cordova-common": "2.1.0",
+        "elementtree": "0.1.6",
+        "nopt": "3.0.6",
+        "properties-parser": "0.2.3",
+        "q": "1.5.0",
+        "shelljs": "0.5.3"
+      },
+      "dependencies": {
+        "abbrev": {
+          "version": "1.1.0",
+          "bundled": true
+        },
+        "android-versions": {
+          "version": "1.2.1",
+          "bundled": true
+        },
+        "ansi": {
+          "version": "0.3.1",
+          "bundled": true
+        },
+        "balanced-match": {
+          "version": "1.0.0",
+          "bundled": true
+        },
+        "base64-js": {
+          "version": "0.0.8",
+          "bundled": true
+        },
+        "big-integer": {
+          "version": "1.6.25",
+          "bundled": true
+        },
+        "bplist-parser": {
+          "version": "0.1.1",
+          "bundled": true,
+          "requires": {
+            "big-integer": "1.6.25"
+          }
+        },
+        "brace-expansion": {
+          "version": "1.1.8",
+          "bundled": true,
+          "requires": {
+            "balanced-match": "1.0.0",
+            "concat-map": "0.0.1"
+          }
+        },
+        "concat-map": {
+          "version": "0.0.1",
+          "bundled": true
+        },
+        "cordova-common": {
+          "version": "2.1.0",
+          "bundled": true,
+          "requires": {
+            "ansi": "0.3.1",
+            "bplist-parser": "0.1.1",
+            "cordova-registry-mapper": "1.1.15",
+            "elementtree": "0.1.6",
+            "glob": "5.0.15",
+            "minimatch": "3.0.4",
+            "osenv": "0.1.4",
+            "plist": "1.2.0",
+            "q": "1.5.0",
+            "semver": "5.4.1",
+            "shelljs": "0.5.3",
+            "underscore": "1.8.3",
+            "unorm": "1.4.1"
+          }
+        },
+        "cordova-registry-mapper": {
+          "version": "1.1.15",
+          "bundled": true
+        },
+        "elementtree": {
+          "version": "0.1.6",
+          "bundled": true,
+          "requires": {
+            "sax": "0.3.5"
+          }
+        },
+        "glob": {
+          "version": "5.0.15",
+          "bundled": true,
+          "requires": {
+            "inflight": "1.0.6",
+            "inherits": "2.0.3",
+            "minimatch": "3.0.4",
+            "once": "1.4.0",
+            "path-is-absolute": "1.0.1"
+          }
+        },
+        "inflight": {
+          "version": "1.0.6",
+          "bundled": true,
+          "requires": {
+            "once": "1.4.0",
+            "wrappy": "1.0.2"
+          }
+        },
+        "inherits": {
+          "version": "2.0.3",
+          "bundled": true
+        },
+        "lodash": {
+          "version": "3.10.1",
+          "bundled": true
+        },
+        "minimatch": {
+          "version": "3.0.4",
+          "bundled": true,
+          "requires": {
+            "brace-expansion": "1.1.8"
+          }
+        },
+        "nopt": {
+          "version": "3.0.6",
+          "bundled": true,
+          "requires": {
+            "abbrev": "1.1.0"
+          }
+        },
+        "once": {
+          "version": "1.4.0",
+          "bundled": true,
+          "requires": {
+            "wrappy": "1.0.2"
+          }
+        },
+        "os-homedir": {
+          "version": "1.0.2",
+          "bundled": true
+        },
+        "os-tmpdir": {
+          "version": "1.0.2",
+          "bundled": true
+        },
+        "osenv": {
+          "version": "0.1.4",
+          "bundled": true,
+          "requires": {
+            "os-homedir": "1.0.2",
+            "os-tmpdir": "1.0.2"
+          }
+        },
+        "path-is-absolute": {
+          "version": "1.0.1",
+          "bundled": true
+        },
+        "plist": {
+          "version": "1.2.0",
+          "bundled": true,
+          "requires": {
+            "base64-js": "0.0.8",
+            "util-deprecate": "1.0.2",
+            "xmlbuilder": "4.0.0",
+            "xmldom": "0.1.27"
+          }
+        },
+        "properties-parser": {
+          "version": "0.2.3",
+          "bundled": true
+        },
+        "q": {
+          "version": "1.5.0",
+          "bundled": true
+        },
+        "sax": {
+          "version": "0.3.5",
+          "bundled": true
+        },
+        "semver": {
+          "version": "5.4.1",
+          "bundled": true
+        },
+        "shelljs": {
+          "version": "0.5.3",
+          "bundled": true
+        },
+        "underscore": {
+          "version": "1.8.3",
+          "bundled": true
+        },
+        "unorm": {
+          "version": "1.4.1",
+          "bundled": true
+        },
+        "util-deprecate": {
+          "version": "1.0.2",
+          "bundled": true
+        },
+        "wrappy": {
+          "version": "1.0.2",
+          "bundled": true
+        },
+        "xmlbuilder": {
+          "version": "4.0.0",
+          "bundled": true,
+          "requires": {
+            "lodash": "3.10.1"
+          }
+        },
+        "xmldom": {
+          "version": "0.1.27",
+          "bundled": true
+        }
+      }
+    },
+    "cordova-plugin-browsertab": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/cordova-plugin-browsertab/-/cordova-plugin-browsertab-0.2.0.tgz",
+      "integrity": "sha1-Hgf5hy4VRnpQ59FKc/79h0+u1ko="
+    },
+    "cordova-plugin-compat": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/cordova-plugin-compat/-/cordova-plugin-compat-1.2.0.tgz",
+      "integrity": "sha1-C8ZXVyduvZIMASzpIOJ0F3V2Nz4="
+    },
+    "cordova-plugin-deeplinks": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/cordova-plugin-deeplinks/-/cordova-plugin-deeplinks-1.1.0.tgz",
+      "integrity": "sha512-xXixVrXfX9lvhMpi1qlzkiTeOmT7P5LfgqIBUCePYSutuscX+SeRGsVM38J8nhpYIefHAJqz7ULr4hT7tunUCA==",
+      "requires": {
+        "mkpath": "1.0.0",
+        "node-version-compare": "1.0.1",
+        "plist": "3.0.1",
+        "rimraf": "2.6.2",
+        "xml2js": "0.4.19"
+      }
+    },
+    "cordova-plugin-whitelist": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/cordova-plugin-whitelist/-/cordova-plugin-whitelist-1.3.3.tgz",
+      "integrity": "sha1-tehezbv+Wu3tQKG/TuI3LmfZb7Q="
+    },
+    "fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+    },
+    "glob": {
+      "version": "7.1.2",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
+      "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
+      "requires": {
+        "fs.realpath": "1.0.0",
+        "inflight": "1.0.6",
+        "inherits": "2.0.3",
+        "minimatch": "3.0.4",
+        "once": "1.4.0",
+        "path-is-absolute": "1.0.1"
+      }
+    },
+    "inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+      "requires": {
+        "once": "1.4.0",
+        "wrappy": "1.0.2"
+      }
+    },
+    "inherits": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+      "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+    },
+    "minimatch": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+      "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+      "requires": {
+        "brace-expansion": "1.1.11"
+      }
+    },
+    "mkpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/mkpath/-/mkpath-1.0.0.tgz",
+      "integrity": "sha1-67Opd+evHGg65v2hK1Raa6bFhT0="
+    },
+    "node-version-compare": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/node-version-compare/-/node-version-compare-1.0.1.tgz",
+      "integrity": "sha1-2Fv9IPCsreM1d/VmgscQnDTFUM0="
+    },
+    "once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+      "requires": {
+        "wrappy": "1.0.2"
+      }
+    },
+    "path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+    },
+    "plist": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/plist/-/plist-3.0.1.tgz",
+      "integrity": "sha512-GpgvHHocGRyQm74b6FWEZZVRroHKE1I0/BTjAmySaohK+cUn+hZpbqXkc3KWgW3gQYkqcQej35FohcT0FRlkRQ==",
+      "requires": {
+        "base64-js": "1.3.0",
+        "xmlbuilder": "9.0.7",
+        "xmldom": "0.1.27"
+      }
+    },
+    "rimraf": {
+      "version": "2.6.2",
+      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
+      "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
+      "requires": {
+        "glob": "7.1.2"
+      }
+    },
+    "sax": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+      "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
+    },
+    "wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+    },
+    "xml2js": {
+      "version": "0.4.19",
+      "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
+      "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
+      "requires": {
+        "sax": "1.2.4",
+        "xmlbuilder": "9.0.7"
+      }
+    },
+    "xmlbuilder": {
+      "version": "9.0.7",
+      "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
+      "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0="
+    },
+    "xmldom": {
+      "version": "0.1.27",
+      "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz",
+      "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk="
+    }
+  }
+}
diff --git a/examples/cordova-native/README.md b/examples/cordova-native/README.md
new file mode 100644
index 0000000..0510ba8
--- /dev/null
+++ b/examples/cordova-native/README.md
@@ -0,0 +1,36 @@
+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
+------------------------------------
+
+    cordova platform add android
+    cordova run android
+
+
+Once the application is opened you can login with username: 'user', and password: 'password'.
+
+
+Troubleshooting
+-----------------------------------------
+
+ * You always need to initialize keycloak after the 'deviceready' event. Otherwise Cordova mode won't be enabled for keycloak.js.
+ * 'http://localhost' should be listed in the allowed redirects in client configuration, but never 'file:///android_asset'.
diff --git a/examples/cordova-native/www/index.html b/examples/cordova-native/www/index.html
new file mode 100644
index 0000000..67b0db8
--- /dev/null
+++ b/examples/cordova-native/www/index.html
@@ -0,0 +1,123 @@
+<!DOCTYPE html>
+<!--
+  ~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+  ~ and other contributors as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~ http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<html>
+<head>
+    <title>Authentication Example</title>
+
+    <meta http-equiv="Content-Security-Policy" content="default-src * gap://ready; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval'">
+
+    <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.onAuthSuccess = updateState;
+        keycloak.onAuthRefreshSuccess = updateState;
+        keycloak.onAuthLogout = updateState;
+
+        function updateState() {
+            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.idTokenParsed.preferred_username;
+                document.getElementById('tokenExpires').innerText = new Date(keycloak.tokenParsed.exp * 1000).toLocaleString();
+                document.getElementById('tokenRefreshExpires').innerText = new Date(keycloak.refreshTokenParsed.exp * 1000).toLocaleString();
+                document.getElementById('token').innerText = JSON.stringify(keycloak.tokenParsed, null, '  ');
+                document.getElementById('idToken').innerText = JSON.stringify(keycloak.idTokenParsed, null, '  ');
+            } else {
+                document.getElementById('authenticated').style.display = 'none';
+                document.getElementById('not-authenticated').style.display = 'block';
+            }
+        }
+
+        function error() {
+            document.getElementById('authenticated').style.display = 'none';
+            document.getElementById('not-authenticated').style.display = 'block';
+            document.getElementById('error').innerText = 'Failed to initialize Keycloak adapter';
+        }
+
+        document.addEventListener("deviceready", function() {
+            keycloak.init({
+              adapter: 'cordova-native',
+              responseMode: 'query',
+              onLoad: 'check-sso',
+              redirectUri: 'android-app://org.keycloak.examples.cordova/https/keycloak-cordova-example.github.io/login'
+            }).success(updateState).error(error);
+        }, false);
+    </script>
+    <style>
+        td {
+            vertical-align: top;
+        }
+
+        tr.odd td {
+            background-color: #eee;
+        }
+    </style>
+</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.updateToken(9999)">Force Refresh token</button>
+        <button onclick="keycloak.accountManagement()">Manage account</button>
+    </div>
+    <div>
+        <table>
+            <tr>
+                <td>Subject</td>
+                <td id="subject"></td>
+            </tr>
+            <tr class="odd">
+                <td>Username</td>
+                <td id="username"></td>
+            </tr>
+            <tr>
+                <td>Token expires</td>
+                <td id="tokenExpires"></td>
+            </tr>
+            <tr class="odd">
+                <td>Refresh token expires</td>
+                <td id="tokenRefreshExpires"></td>
+            </tr>
+            <tr>
+                <td>Token</td>
+                <td><pre id="token"></pre></td>
+            </tr>
+            <tr class="odd">
+                <td>ID Token</td>
+                <td><pre id="idToken"></pre></td>
+            </tr>
+        </table>
+    </div>
+</div>
+<div id="not-authenticated" style="display: none;">
+    <div>
+        <button onclick="keycloak.login()">Log in</button>
+    </div>
+    <div>
+        <p id="error">Not authenticated</p>
+    </div>
+</div>
+</body>
+</html>
+