keycloak-memoizeit

Details

diff --git a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
index 7b7fa9d..878092e 100755
--- a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
@@ -21,6 +21,7 @@ public class ApplicationRepresentation {
     protected List<UserRoleMappingRepresentation> roleMappings;
     protected List<ScopeMappingRepresentation> scopeMappings;
     protected List<String> redirectUris;
+    protected List<String> webOrigins;
 
     public String getSelf() {
         return self;
@@ -155,4 +156,12 @@ public class ApplicationRepresentation {
     public void setRedirectUris(List<String> redirectUris) {
         this.redirectUris = redirectUris;
     }
+
+    public List<String> getWebOrigins() {
+        return webOrigins;
+    }
+
+    public void setWebOrigins(List<String> webOrigins) {
+        this.webOrigins = webOrigins;
+    }
 }

examples/js/keycloak.js 216(+97 -119)

diff --git a/examples/js/keycloak.js b/examples/js/keycloak.js
index dfb6ebb..480996d 100644
--- a/examples/js/keycloak.js
+++ b/examples/js/keycloak.js
@@ -1,120 +1,98 @@
-window.keycloak = (function() {
-	var kc = {};
-	var config = null;
-
-	kc.init = function(c) {
-		config = c;
-
-		var token = getTokenFromCode();
-		if (token) {
-			var t = parseToken(token);
-			kc.user = t.prn;
-			kc.authenticated = true;
-		} else {
-			kc.authenticated = false;
-		}
-	}
-
-	kc.login = function() {
-		var clientId = encodeURIComponent(config.clientId);
-		var redirectUri = encodeURIComponent(window.location.href);
-		var state = encodeURIComponent(createUUID());
-		var realm = encodeURIComponent(config.realm);
-		var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/login?response_type=code&client_id=' + clientId + '&redirect_uri=' + redirectUri
-				+ '&state=' + state;
-		window.location.href = url;
-	}
-
-	return kc;
-
-	function parseToken(token) {
-		var t = base64Decode(token.split('.')[1]);
-		return JSON.parse(t);
-	}
-
-	function getTokenFromCode() {
-		var code = getQueryParam('code');
-		if (code) {
-			window.history.replaceState({}, document.title, location.protocol + "//" + location.host + location.pathname);
-			
-			var clientId = encodeURIComponent(config.clientId);
-			var clientSecret = encodeURIComponent(config.clientSecret);
-			var realm = encodeURIComponent(config.realm);
-
-			var params = 'code=' + code + '&client_id=' + config.clientId + '&password=' + config.clientSecret;
-			var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/access/codes'
-
-			var http = new XMLHttpRequest();
-			http.open('POST', url, false);
-			http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
-
-			http.send(params);
-			if (http.status == 200) {
-				return JSON.parse(http.responseText)['access_token'];
-			}
-		}
-		return undefined;
-	}
-
-	function getQueryParam(name) {
-		var params = window.location.search.substring(1).split('&');
-		for ( var i = 0; i < params.length; i++) {
-			var p = params[i].split('=');
-			if (decodeURIComponent(p[0]) == name) {
-				return p[1];
-			}
-		}
-	}
-
-	function createUUID() {
-		var s = [];
-		var hexDigits = '0123456789abcdef';
-		for ( var i = 0; i < 36; i++) {
-			s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
-		}
-		s[14] = '4';
-		s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
-		s[8] = s[13] = s[18] = s[23] = '-';
-		var uuid = s.join('');
-		return uuid;
-	}
-
-	function base64Decode (data) {
-		  var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
-		  var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
-		    ac = 0,
-		    dec = "",
-		    tmp_arr = [];
-
-		  if (!data) {
-		    return data;
-		  }
-
-		  data += '';
-
-		  do {
-		    h1 = b64.indexOf(data.charAt(i++));
-		    h2 = b64.indexOf(data.charAt(i++));
-		    h3 = b64.indexOf(data.charAt(i++));
-		    h4 = b64.indexOf(data.charAt(i++));
-
-		    bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
-
-		    o1 = bits >> 16 & 0xff;
-		    o2 = bits >> 8 & 0xff;
-		    o3 = bits & 0xff;
-
-		    if (h3 == 64) {
-		      tmp_arr[ac++] = String.fromCharCode(o1);
-		    } else if (h4 == 64) {
-		      tmp_arr[ac++] = String.fromCharCode(o1, o2);
-		    } else {
-		      tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);
-		    }
-		  } while (i < data.length);
-
-		  dec = tmp_arr.join('');
-
-		  return dec;
-		}
+window.keycloak = (function () {
+    var kc = {};
+    var config = {
+        baseUrl: null,
+        clientId: null,
+        clientSecret: null,
+        realm: null
+    };
+
+    kc.init = function (c) {
+        for (var prop in config) {
+            if (c[prop]) {
+                config[prop] = c[prop];
+            }
+
+            if (!config[prop]) {
+                throw new Error(prop + 'not defined');
+            }
+        }
+
+        var token = getTokenFromCode();
+        if (token) {
+            var t = parseToken(token);
+            kc.user = t.prn;
+            kc.authenticated = true;
+            kc.token = token;
+        } else {
+            kc.authenticated = false;
+        }
+    }
+
+    kc.login = function () {
+        var clientId = encodeURIComponent(config.clientId);
+        var redirectUri = encodeURIComponent(window.location.href);
+        var state = encodeURIComponent(createUUID());
+        var realm = encodeURIComponent(config.realm);
+        var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/login?response_type=code&client_id=' + clientId + '&redirect_uri=' + redirectUri
+            + '&state=' + state;
+
+        sessionStorage.state = state;
+
+        window.location.href = url;
+    }
+
+    return kc;
+
+    function parseToken(token) {
+        return JSON.parse(atob(token.split('.')[1]));
+    }
+
+    function getTokenFromCode() {
+        var code = getQueryParam('code');
+        var state = getQueryParam('state');
+        if (code && state === sessionStorage.state) {
+            window.history.replaceState({}, document.title, location.protocol + "//" + location.host + location.pathname);
+
+            var clientId = encodeURIComponent(config.clientId);
+            var clientSecret = encodeURIComponent(config.clientSecret);
+            var realm = encodeURIComponent(config.realm);
+
+            var params = 'code=' + code + '&client_id=' + clientId + '&password=' + clientSecret;
+            var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/access/codes'
+
+            var http = new XMLHttpRequest();
+            http.open('POST', url, false);
+            http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
+
+            http.send(params);
+            if (http.status == 200) {
+                return JSON.parse(http.responseText)['access_token'];
+            }
+        }
+        return undefined;
+    }
+
+    function getQueryParam(name) {
+        var params = window.location.search.substring(1).split('&');
+        for (var i = 0; i < params.length; i++) {
+            var p = params[i].split('=');
+            if (decodeURIComponent(p[0]) == name) {
+                return p[1];
+            }
+        }
+    }
+
+    function createUUID() {
+        var s = [];
+        var hexDigits = '0123456789abcdef';
+        for (var i = 0; i < 36; i++) {
+            s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
+        }
+        s[14] = '4';
+        s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
+        s[8] = s[13] = s[18] = s[23] = '-';
+        var uuid = s.join('');
+        return uuid;
+    }
 })();
\ No newline at end of file
diff --git a/examples/js/testrealm.json b/examples/js/testrealm.json
index 824fe14..2468f48 100755
--- a/examples/js/testrealm.json
+++ b/examples/js/testrealm.json
@@ -48,6 +48,7 @@
             "enabled": true,
             "adminUrl": "http://localhost:8081/app/logout",
             "useRealmMappings": true,
+            "webOrigins": [ "http://localhost", "http://localhost:8000", "http://localhost:8080" ],
             "credentials": [
                 {
                     "type": "password",
diff --git a/examples/js-google/index.html b/examples/js-google/index.html
new file mode 100644
index 0000000..7a72120
--- /dev/null
+++ b/examples/js-google/index.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="keycloak.js"></script>
+</head>
+<body>
+	<script>
+		keycloak.init({
+			clientId : '57572475438.apps.googleusercontent.com',
+			clientSecret : 'xyfsPS9maRTz5fj0pOxf0zjD'
+		});
+
+		if (keycloak.authenticated) {
+            document.write('<h2>Token</h2><pre>' + keycloak.token + '</pre>');
+            document.write('<h2>Token info</h2><pre>' + JSON.stringify(keycloak.tokenInfo, undefined, 4) + '</pre>');
+            document.write('<h2>Profile</h2><pre>' + JSON.stringify(keycloak.profile(true), undefined, 4) + '</pre>');		
+            document.write('<h2>Contacts</h2><pre>' + keycloak.contacts(true) + '</pre>');		
+        } else {
+			document.write('<a href="#" id="login" onclick="keycloak.login()">Login</a>');
+		}
+	</script>
+
+</body>
+</html>
diff --git a/examples/js-google/keycloak.js b/examples/js-google/keycloak.js
new file mode 100644
index 0000000..172557d
--- /dev/null
+++ b/examples/js-google/keycloak.js
@@ -0,0 +1,139 @@
+window.keycloak = (function () {
+    var kc = {};
+    var config = {
+        clientId: null,
+        clientSecret: null
+    };
+
+    kc.init = function (c) {
+        for (var prop in config) {
+            if (c[prop]) {
+                config[prop] = c[prop];
+            }
+
+            if (!config[prop]) {
+                throw new Error(prop + ' not defined');
+            }
+        }
+
+        loadToken();
+
+        if (kc.token) {
+            kc.user = kc.tokenInfo.user_id;
+            kc.authenticated = true;
+        } else {
+            kc.authenticated = false;
+            kc.user = null;
+        }
+    }
+
+    kc.login = function () {
+        var clientId = encodeURIComponent(config.clientId);
+        var redirectUri = encodeURIComponent(window.location.href);
+        var state = encodeURIComponent(createUUID());
+        var scope = encodeURIComponent('https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/plus.login');
+        var url = 'https://accounts.google.com/o/oauth2/auth?response_type=token&client_id=' + clientId + '&redirect_uri=' + redirectUri
+            + '&state=' + state + '&scope=' + scope;
+
+        sessionStorage.state = state;
+
+        window.location.href = url;
+    }
+
+    function parseToken(token) {
+        return JSON.parse(atob(token.split('.')[1]));
+    }
+
+    kc.profile = function(header) {
+        var url = 'https://www.googleapis.com/oauth2/v1/userinfo'
+
+        if (!header) {
+            url = url + '?access_token=' + kc.token;
+        }
+
+         var http = new XMLHttpRequest();
+         http.open('GET', url, false);
+        if (header) {
+        http.setRequestHeader('Authorization', 'Bearer ' + kc.token);
+        }       
+
+            http.send();
+            if (http.status == 200) {
+                return JSON.parse(http.responseText);
+            }
+    }
+
+    kc.contacts = function(header) {
+        var url = 'https://www.googleapis.com/plus/v1/people/me';
+
+        if (!header) {
+            url = url + '?access_token=' + kc.token;
+        }
+
+         var http = new XMLHttpRequest();
+         http.open('GET', url, false);
+        if (header) {
+        http.setRequestHeader('Authorization', 'Bearer ' + kc.token);
+        }       
+
+            http.send();
+            if (http.status == 200) {
+                return http.responseText;
+            }
+    }
+
+    return kc;
+
+    function loadToken() {
+        var params = {}
+        var queryString = location.hash.substring(1)
+        var regex = /([^&=]+)=([^&]*)/g, m;
+        while (m = regex.exec(queryString)) {
+            params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
+        }
+
+        var token = params['access_token'];
+        var state = params['state'];
+
+        if (token && state === sessionStorage.state) {
+            window.history.replaceState({}, document.title, location.protocol + "//" + location.host + location.pathname);
+
+                kc.token = token;
+
+            var url = 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' + token;
+
+            var http = new XMLHttpRequest();
+            http.open('GET', url, false);
+
+            http.send();
+            if (http.status == 200) {
+                kc.tokenInfo = JSON.parse(http.responseText);
+            }
+        }
+        return undefined;
+    }
+
+    function getQueryParam(name) {
+    console.debug(window.location.hash);
+        var params = window.location.hash.substring(1).split('&');
+        for (var i = 0; i < params.length; i++) {
+            var p = params[i].split('=');
+            if (decodeURIComponent(p[0]) == name) {
+                return p[1];
+            }
+        }
+    }
+
+    function createUUID() {
+        var s = [];
+        var hexDigits = '0123456789abcdef';
+        for (var i = 0; i < 36; i++) {
+            s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
+        }
+        s[14] = '4';
+        s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
+        s[8] = s[13] = s[18] = s[23] = '-';
+        var uuid = s.join('');
+        return uuid;
+    }
+})();
diff --git a/examples/js-google/keycloak.js.orig b/examples/js-google/keycloak.js.orig
new file mode 100644
index 0000000..439d2af
--- /dev/null
+++ b/examples/js-google/keycloak.js.orig
@@ -0,0 +1,222 @@
+<<<<<<< Updated upstream
+window.keycloak = (function() {
+	var kc = {};
+	var config = null;
+
+	kc.init = function(c) {
+		config = c;
+
+		var token = getTokenFromCode();
+		if (token) {
+			var t = parseToken(token);
+			kc.user = t.prn;
+			kc.authenticated = true;
+		} else {
+			kc.authenticated = false;
+		}
+	}
+
+	kc.login = function() {
+		var clientId = encodeURIComponent(config.clientId);
+		var redirectUri = encodeURIComponent(window.location.href);
+		var state = encodeURIComponent(createUUID());
+		var realm = encodeURIComponent(config.realm);
+		var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/login?response_type=code&client_id=' + clientId + '&redirect_uri=' + redirectUri
+				+ '&state=' + state;
+		window.location.href = url;
+	}
+
+	return kc;
+
+	function parseToken(token) {
+		return JSON.parse(atob(token.split('.')[1]));
+	}
+
+	function getTokenFromCode() {
+		var code = getQueryParam('code');
+		if (code) {
+			window.history.replaceState({}, document.title, location.protocol + "//" + location.host + location.pathname);
+
+			var clientId = encodeURIComponent(config.clientId);
+			var clientSecret = encodeURIComponent(config.clientSecret);
+			var realm = encodeURIComponent(config.realm);
+
+			var params = 'code=' + code + '&client_id=' + config.clientId + '&password=' + config.clientSecret;
+			var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/access/codes'
+
+			var http = new XMLHttpRequest();
+			http.open('POST', url, false);
+			http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
+
+			http.send(params);
+			if (http.status == 200) {
+				return JSON.parse(http.responseText)['access_token'];
+			}
+		}
+		return undefined;
+	}
+
+	function getQueryParam(name) {
+		var params = window.location.search.substring(1).split('&');
+		for ( var i = 0; i < params.length; i++) {
+			var p = params[i].split('=');
+			if (decodeURIComponent(p[0]) == name) {
+				return p[1];
+			}
+		}
+	}
+
+	function createUUID() {
+		var s = [];
+		var hexDigits = '0123456789abcdef';
+		for ( var i = 0; i < 36; i++) {
+			s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
+		}
+		s[14] = '4';
+		s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
+		s[8] = s[13] = s[18] = s[23] = '-';
+		var uuid = s.join('');
+		return uuid;
+	}
+=======
+window.keycloak = (function () {
+    var kc = {};
+    var config = {
+        baseUrl : null,
+        clientId : null,
+        clientSecret: null,
+        realm: null
+    };
+
+    kc.init = function (c) {
+        for (var prop in config) {
+            if (c[prop]) {
+                config[prop] = c[prop];
+            }
+
+            if (!config[prop]) {
+                throw new Error(prop + 'not defined');
+            }
+        }
+
+        var token = getTokenFromCode();
+        if (token) {
+            var t = parseToken(token);
+            kc.user = t.prn;
+            kc.authenticated = true;
+        } else {
+            kc.authenticated = false;
+        }
+    }
+
+    kc.login = function () {
+        var clientId = encodeURIComponent(config.clientId);
+        var redirectUri = encodeURIComponent(window.location.href);
+        var realm = encodeURIComponent(config.realm);
+        var state = encodeURIComponent(createUUID());
+        var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/login?response_type=code&client_id=' + clientId + '&redirect_uri=' + redirectUri
+            + '&state=' + state;
+
+        sessionStorage.state = state;
+
+        window.location.href = url;
+    }
+
+    return kc;
+
+    function parseToken(token) {
+        var t = base64Decode(token.split('.')[1]);
+        return JSON.parse(t);
+    }
+
+    function getTokenFromCode() {
+        var code = getQueryParam('code');
+        var state = getQueryParam('state');
+
+        if (code) {
+            if (state && state === sessionStorage.state) {
+                window.history.replaceState({}, document.title, location.protocol + "//" + location.host + location.pathname);
+
+                var clientId = encodeURIComponent(config.clientId);
+                var clientSecret = encodeURIComponent(config.clientSecret);
+                var realm = encodeURIComponent(config.realm);
+
+                var params = 'code=' + code + '&client_id=' + clientId + '&password=' + clientSecret;
+                var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/access/codes'
+
+                var http = new XMLHttpRequest();
+                http.open('POST', url, false);
+                http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
+
+                http.send(params);
+                if (http.status == 200) {
+                    return JSON.parse(http.responseText)['access_token'];
+                }
+            }
+        }
+        return undefined;
+    }
+
+    function getQueryParam(name) {
+        var params = window.location.search.substring(1).split('&');
+        for (var i = 0; i < params.length; i++) {
+            var p = params[i].split('=');
+            if (decodeURIComponent(p[0]) == name) {
+                return p[1];
+            }
+        }
+    }
+
+    function createUUID() {
+        var s = [];
+        var hexDigits = '0123456789abcdef';
+        for (var i = 0; i < 36; i++) {
+            s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
+        }
+        s[14] = '4';
+        s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
+        s[8] = s[13] = s[18] = s[23] = '-';
+        var uuid = s.join('');
+        return uuid;
+    }
+
+    function base64Decode(data) {
+        var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+        var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
+            ac = 0,
+            dec = "",
+            tmp_arr = [];
+
+        if (!data) {
+            return data;
+        }
+
+        data += '';
+
+        do {
+            h1 = b64.indexOf(data.charAt(i++));
+            h2 = b64.indexOf(data.charAt(i++));
+            h3 = b64.indexOf(data.charAt(i++));
+            h4 = b64.indexOf(data.charAt(i++));
+
+            bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
+
+            o1 = bits >> 16 & 0xff;
+            o2 = bits >> 8 & 0xff;
+            o3 = bits & 0xff;
+
+            if (h3 == 64) {
+                tmp_arr[ac++] = String.fromCharCode(o1);
+            } else if (h4 == 64) {
+                tmp_arr[ac++] = String.fromCharCode(o1, o2);
+            } else {
+                tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);
+            }
+        } while (i < data.length);
+
+        dec = tmp_arr.join('');
+
+        return dec;
+    }
+>>>>>>> Stashed changes
+})();
\ No newline at end of file
diff --git a/examples/js-google/kinvey.html b/examples/js-google/kinvey.html
new file mode 100644
index 0000000..9e1324c
--- /dev/null
+++ b/examples/js-google/kinvey.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="http://code.jquery.com/jquery-2.0.3.js"></script>
+</head>
+<body>
+
+    <script>
+        $.ajax('https://baas.kinvey.com/appdata/kid_PVD-jo1HqO');
+    </script>
+
+</body>
+</html>
diff --git a/examples/js-google/testrealm.json b/examples/js-google/testrealm.json
new file mode 100755
index 0000000..2468f48
--- /dev/null
+++ b/examples/js-google/testrealm.json
@@ -0,0 +1,60 @@
+{
+    "id": "test",
+    "realm": "test",
+    "enabled": true,
+    "tokenLifespan": 300,
+    "accessCodeLifespan": 10,
+    "accessCodeLifespanUserAction": 600,
+    "sslNotRequired": true,
+    "cookieLoginAllowed": true,
+    "registrationAllowed": true,
+    "resetPasswordAllowed": 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" ],
+    "requiredApplicationCredentials": [ "password" ],
+    "requiredOAuthClientCredentials": [ "password" ],
+    "defaultRoles": [ "user" ],
+    "users" : [
+        {
+            "username" : "test-user@localhost",
+            "enabled": true,
+            "email" : "test-user@localhost",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ]
+        }
+    ],
+    "roles": [
+        {
+            "name": "user",
+            "description": "Have User privileges"
+        },
+        {
+            "name": "admin",
+            "description": "Have Administrator privileges"
+        }
+    ],
+    "roleMappings": [
+        {
+            "username": "test-user@localhost",
+            "roles": ["user"]
+        }
+    ],
+    "applications": [
+        {
+            "name": "test-app",
+            "enabled": true,
+            "adminUrl": "http://localhost:8081/app/logout",
+            "useRealmMappings": true,
+            "webOrigins": [ "http://localhost", "http://localhost:8000", "http://localhost:8080" ],
+            "credentials": [
+                {
+                    "type": "password",
+                    "value": "password"
+                }
+            ]
+        }
+    ]
+}
diff --git a/model/api/src/main/java/org/keycloak/models/UserModel.java b/model/api/src/main/java/org/keycloak/models/UserModel.java
index 8598ae7..7e33185 100755
--- a/model/api/src/main/java/org/keycloak/models/UserModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserModel.java
@@ -35,6 +35,14 @@ public interface UserModel {
 
     void removeRequiredAction(RequiredAction action);
 
+    Set<String> getWebOrigins();
+
+    void setWebOrigins(Set<String> webOrigins);
+
+    void addWebOrigin(String webOrigin);
+
+    void removeWebOrigin(String webOrigin);
+
     Set<String> getRedirectUris();
 
     void setRedirectUris(Set<String> redirectUris);
diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/UserAdapter.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/UserAdapter.java
index 98894ce..e44c92a 100755
--- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/UserAdapter.java
+++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/UserAdapter.java
@@ -22,6 +22,7 @@ public class UserAdapter implements UserModel {
     private static final String REQUIRED_ACTIONS_ATTR = "requiredActions";
 
     private static final String REDIRECT_URIS = "redirectUris";
+    private static final String WEB_ORIGINS = "webOrigins";
 
     protected User user;
     protected IdentityManager idm;
@@ -162,6 +163,26 @@ public class UserAdapter implements UserModel {
     }
 
     @Override
+    public Set<String> getWebOrigins() {
+        return getAttributeSet(WEB_ORIGINS);
+    }
+
+    @Override
+    public void setWebOrigins(Set<String> webOrigins) {
+        setAttributeSet(WEB_ORIGINS, webOrigins);
+    }
+
+    @Override
+    public void addWebOrigin(String webOrigin) {
+        addToAttributeSet(WEB_ORIGINS, webOrigin);
+    }
+
+    @Override
+    public void removeWebOrigin(String webOrigin) {
+        removeFromAttributeSet(WEB_ORIGINS, webOrigin);
+    }
+
+    @Override
     public boolean isTotp() {
         Attribute<Boolean> a = user.getAttribute(KEYCLOAK_TOTP_ATTR);
         return a != null ? a.getValue() : false;
diff --git a/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java b/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java
index 93c2959..9b89cf4 100755
--- a/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java
@@ -42,6 +42,11 @@ public class ApplicationManager {
                 resourceUser.addRedirectUri(redirectUri);
             }
         }
+        if (resourceRep.getWebOrigins() != null) {
+            for (String webOrigin : resourceRep.getWebOrigins()) {
+                resourceUser.addWebOrigin(webOrigin);
+            }
+        }
 
         realm.grantRole(resourceUser, loginRole);
 
@@ -97,6 +102,11 @@ public class ApplicationManager {
         if (redirectUris != null) {
             resource.getApplicationUser().setRedirectUris(new HashSet<String>(redirectUris));
         }
+
+        List<String> webOrigins = rep.getWebOrigins();
+        if (webOrigins != null) {
+            resource.getApplicationUser().setWebOrigins(new HashSet<String>(webOrigins));
+        }
     }
 
     public ApplicationRepresentation toRepresentation(ApplicationModel applicationModel) {
@@ -113,6 +123,11 @@ public class ApplicationManager {
             rep.setRedirectUris(new LinkedList<String>(redirectUris));
         }
 
+        Set<String> webOrigins = applicationModel.getApplicationUser().getWebOrigins();
+        if (webOrigins != null) {
+            rep.setWebOrigins(new LinkedList<String>(webOrigins));
+        }
+
         return rep;
 
     }
diff --git a/services/src/main/java/org/keycloak/services/resources/Cors.java b/services/src/main/java/org/keycloak/services/resources/Cors.java
new file mode 100644
index 0000000..8c28d6f
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/Cors.java
@@ -0,0 +1,43 @@
+package org.keycloak.services.resources;
+
+import java.util.Set;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
+
+import org.jboss.resteasy.spi.HttpRequest;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class Cors {
+
+    private HttpRequest request;
+    private ResponseBuilder response;
+    private Set<String> allowedOrigins;
+
+    public Cors(HttpRequest request, ResponseBuilder response) {
+        this.request = request;
+        this.response = response;
+    }
+
+    public static Cors add(HttpRequest request, ResponseBuilder response) {
+        return new Cors(request, response);
+    }
+
+    public Cors allowedOrigins(Set<String> allowedOrigins) {
+        this.allowedOrigins = allowedOrigins;
+        return this;
+    }
+
+    public Response build() {
+        String origin = request.getHttpHeaders().getHeaderString("Origin");
+        if (origin == null || allowedOrigins == null || (!allowedOrigins.contains(origin))) {
+            return response.build();
+        }
+
+        response.header("Access-Control-Allow-Origin", origin);
+        return response.build();
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index d72e80d..7296d8e 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -415,7 +415,8 @@ public class TokenService {
         }
         logger.info("accessRequest SUCCESS");
         AccessTokenResponse res = accessTokenResponse(realm.getPrivateKey(), accessCode.getToken());
-        return Response.ok(res).build();
+
+        return Cors.add(request, Response.ok(res)).allowedOrigins(client.getWebOrigins()).build();
     }
 
     protected AccessTokenResponse accessTokenResponse(PrivateKey privateKey, SkeletonKeyToken token) {