keycloak-aplcache

Changes

examples/pom.xml 5(+1 -4)

Details

diff --git a/core/src/main/java/org/keycloak/AbstractOAuthClient.java b/core/src/main/java/org/keycloak/AbstractOAuthClient.java
index bf40101..955f9df 100755
--- a/core/src/main/java/org/keycloak/AbstractOAuthClient.java
+++ b/core/src/main/java/org/keycloak/AbstractOAuthClient.java
@@ -28,6 +28,7 @@ public class AbstractOAuthClient {
     protected String codeUrl;
     protected String stateCookieName = "OAuth_Token_Request_State";
     protected Client client;
+    protected boolean isSecure;
     protected final AtomicLong counter = new AtomicLong();
 
     protected String getStateCode() {
@@ -109,6 +110,8 @@ public class AbstractOAuthClient {
         Form codeForm = new Form()
                 .param("grant_type", "authorization_code")
                 .param("code", code)
+                .param("client_id", clientId)
+                .param("Password", password)
                 .param("redirect_uri", redirectUri);
         Response res = client.target(codeUrl).request().header(HttpHeaders.AUTHORIZATION, authHeader).post(Entity.form(codeForm));
         try {
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index cddd1ca..080d845 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -18,7 +18,7 @@ public class RealmRepresentation {
     protected boolean cookieLoginAllowed;
     protected String privateKey;
     protected String publicKey;
-    protected Set<String> roles;
+    protected List<RoleRepresentation> roles;
     protected List<RequiredCredentialRepresentation> requiredCredentials;
     protected List<UserRepresentation> users;
     protected List<RoleMappingRepresentation> roleMappings;
@@ -146,11 +146,11 @@ public class RealmRepresentation {
         this.accessCodeLifespan = accessCodeLifespan;
     }
 
-    public Set<String> getRoles() {
+    public List<RoleRepresentation> getRoles() {
         return roles;
     }
 
-    public void setRoles(Set<String> roles) {
+    public void setRoles(List<RoleRepresentation> roles) {
         this.roles = roles;
     }
 
diff --git a/core/src/main/java/org/keycloak/representations/idm/ResourceRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ResourceRepresentation.java
index 15c49f9..84d499f 100755
--- a/core/src/main/java/org/keycloak/representations/idm/ResourceRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/ResourceRepresentation.java
@@ -16,7 +16,7 @@ public class ResourceRepresentation {
     protected boolean surrogateAuthRequired;
     protected boolean useRealmMappings;
     protected List<CredentialRepresentation> credentials;
-    protected Set<String> roles;
+    protected List<RoleRepresentation> roles;
     protected List<RoleMappingRepresentation> roleMappings;
     protected List<ScopeMappingRepresentation> scopeMappings;
 
@@ -44,17 +44,17 @@ public class ResourceRepresentation {
         this.surrogateAuthRequired = surrogateAuthRequired;
     }
 
-    public Set<String> getRoles() {
+    public List<RoleRepresentation> getRoles() {
         return roles;
     }
 
-    public void setRoles(Set<String> roles) {
+    public void setRoles(List<RoleRepresentation> roles) {
         this.roles = roles;
     }
 
-    public ResourceRepresentation role(String role) {
-        if (this.roles == null) this.roles = new HashSet<String>();
-        this.roles.add(role);
+    public ResourceRepresentation role(String role, String description) {
+        if (this.roles == null) this.roles = new ArrayList<RoleRepresentation>();
+        this.roles.add(new RoleRepresentation(role, description));
         return this;
     }
 
diff --git a/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java
new file mode 100755
index 0000000..60fbc6b
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java
@@ -0,0 +1,34 @@
+package org.keycloak.representations.idm;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RoleRepresentation {
+    protected String name;
+    protected String description;
+
+    public RoleRepresentation() {
+    }
+
+    public RoleRepresentation(String name, String description) {
+        this.name = name;
+        this.description = description;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+}
diff --git a/core/src/main/java/org/keycloak/servlet/ServletOAuthClient.java b/core/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
index 3ba7224..27693b4 100755
--- a/core/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
+++ b/core/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
@@ -55,7 +55,7 @@ public class ServletOAuthClient extends AbstractOAuthClient {
         if (cookiePath.equals("")) cookiePath = "/";
 
         Cookie cookie = new Cookie(stateCookieName, state);
-        cookie.setSecure(true);
+        cookie.setSecure(isSecure);
         cookie.setPath(cookiePath);
         response.addCookie(cookie);
         response.sendRedirect(url.toString());
diff --git a/examples/as7-eap-demo/pom.xml b/examples/as7-eap-demo/pom.xml
new file mode 100755
index 0000000..a147298
--- /dev/null
+++ b/examples/as7-eap-demo/pom.xml
@@ -0,0 +1,43 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+        <artifactId>keycloak-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.0-alpha-1</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+    <name>Examples</name>
+    <description/>
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.keycloak</groupId>
+    <artifactId>as7-eap-demo-pom</artifactId>
+    <packaging>pom</packaging>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-deploy-plugin</artifactId>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.jboss.as.plugins</groupId>
+                <artifactId>jboss-as-maven-plugin</artifactId>
+                <version>7.1.1.Final</version>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    <modules>
+        <module>server</module>
+        <module>customer-app</module>
+        <module>product-app</module>
+        <module>database-service</module>
+        <module>third-party</module>
+    </modules>
+</project>
diff --git a/examples/as7-eap-demo/README.md b/examples/as7-eap-demo/README.md
new file mode 100755
index 0000000..1910c91
--- /dev/null
+++ b/examples/as7-eap-demo/README.md
@@ -0,0 +1,61 @@
+Login, Distributed SSO, Distributed Logout, and Oauth Token Grant AS7 Examples
+===================================
+The following examples requires JBoss AS7 or EAP 6.1, and Resteasy 3.0.2 and has been tested on version EAP 6.1.  Here's the highlights of the examples
+* Delegating authentication of a web app to the remote authentication server via OAuth 2 protocols
+* Distributed Single-Sign-On and Single-Logout
+* Transferring identity and role mappings via a special bearer token (Skeleton Key Token).
+* Bearer token authentication and authorization of JAX-RS services
+* Obtaining bearer tokens via the OAuth2 protocol
+
+There are 5 WAR projects.  These all will run on the same jboss instance, but pretend each one is running on a different
+machine on the network or Internet.
+* **auth-server**: This is the keycloak SSO auth server
+* **customer-app** A WAR applications that does remote login using OAUTH2 browser redirects with the auth server
+* **product-app** A WAR applications that does remote login using OAUTH2 browser redirects with the auth server
+* **database-service** JAX-RS services authenticated by bearer tokens only.  The customer and product app invoke on it
+  to get data
+* **third-party** Simple WAR that obtain a bearer token using OAuth2 using browser redirects to the auth-server.
+
+The UI of each of these applications is very crude and exists just to show our OAuth2 implementation in action.
+
+
+Step 1: Make sure you've upgraded Resteasy
+--------------------------------------
+The first thing you is upgrade Resteasy to 3.0.2 within JBoss as described [here](http://docs.jboss.org/resteasy/docs/3.0.2.Final/userguide/html/Installation_Configuration.html#upgrading-as7)
+
+
+Step 2: Boot JBoss
+---------------------------------------
+Boot JBoss in 'standalone' mode.
+
+Step 3: Build and deploy
+---------------------------------------
+next you must build and deploy
+
+1. cd as7-eap-demo
+2. mvn clean install
+3. mvn jboss-as:deploy
+
+Step 4: Login and Observe Apps
+---------------------------------------
+Try going to the customer app and viewing customer data:
+
+[http://localhost:8080/customer-portal/customers/view.jsp](http://localhost:8080/customer-portal/customers/view.jsp)
+
+This should take you to the auth-server login screen.  Enter username: bburke@redhat.com and password: password.
+
+If you click on the products link, you'll be take to the products app and show a product listing.  The redirects
+are still happening, but the auth-server knows you are already logged in so the login is bypassed.
+
+If you click on the logout link of either of the product or customer app, you'll be logged out of all the applications.
+
+Step 5: Traditional OAuth2 Example
+----------------------------------
+The customer and product apps are logins.  The third-party app is the traditional OAuth2 usecase of a client wanting
+to get permission to access a user's data.  To run this example
+
+[http://localhost:8080/oauth-client](http://localhost:8080/oauth-client)
+
+If you area already logged in, you will not be asked for a username and password, but you will be redirected to
+an oauth grant page.  This page asks you if you want to grant certain permissions to the third-part app.
+
diff --git a/examples/as7-eap-demo/server/src/main/webapp/loginForm.jsp b/examples/as7-eap-demo/server/src/main/webapp/loginForm.jsp
index b3ee74a..0e1b963 100755
--- a/examples/as7-eap-demo/server/src/main/webapp/loginForm.jsp
+++ b/examples/as7-eap-demo/server/src/main/webapp/loginForm.jsp
@@ -9,7 +9,7 @@
 
 <head>
     <meta charset="utf-8">
-    <title>Keycloak</title>
+    <title>Keycloak Realm Login Page</title>
 
     <link rel="shortcut icon" type="image/x-icon" href="<%=application.getContextPath()%>/img/favicon.ico">
 
diff --git a/examples/as7-eap-demo/server/src/main/webapp/META-INF/testrealm.json b/examples/as7-eap-demo/server/src/main/webapp/META-INF/testrealm.json
index db10768..b81aad6 100755
--- a/examples/as7-eap-demo/server/src/main/webapp/META-INF/testrealm.json
+++ b/examples/as7-eap-demo/server/src/main/webapp/META-INF/testrealm.json
@@ -25,12 +25,34 @@
                 { "type" : "Password",
                  "value" : "password" }
             ]
+      },
+      {
+            "username" : "third-party",
+            "enabled" : true,
+            "credentials" : [
+                { "type" : "Password",
+                 "value" : "password" }
+            ]
       }
    ],
+   "roles" : [
+      { "name" : "user", "description" : "Have User privileges" },
+      { "name" : "admin", "description" : "Have Administrator privileges" }
+   ],
    "roleMappings" : [
        {
           "username" : "bburke@redhat.com",
           "roles" : ["user"]
+       },
+       {
+          "username" : "third-party",
+          "roles" : ["KEYCLOAK_IDENTITY_REQUESTER"]
+       }
+   ],
+   "scopeMappings" : [
+       {
+          "username" : "third-party",
+          "roles" : ["user"]
        }
    ],
    "resources" : [
diff --git a/examples/as7-eap-demo/server/src/main/webapp/oauthGrantForm.jsp b/examples/as7-eap-demo/server/src/main/webapp/oauthGrantForm.jsp
new file mode 100755
index 0000000..07c1d7e
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/oauthGrantForm.jsp
@@ -0,0 +1,82 @@
+<%@ page import="org.picketlink.idm.model.*,org.keycloak.services.models.*,org.keycloak.services.resources.*,javax.ws.rs.core.*,java.util.*" language="java" contentType="text/html; charset=ISO-8859-1"
+ pageEncoding="ISO-8859-1"%>
+<%
+        RealmModel realm = (RealmModel)request.getAttribute(RealmModel.class.getName());
+        String username = (String)request.getAttribute("username");
+%>
+<!doctype html>
+<html lang="en">
+
+<head>
+    <meta charset="utf-8">
+    <title>Keycloak</title>
+
+    <link rel="shortcut icon" type="image/x-icon" href="<%=application.getContextPath()%>/img/favicon.ico">
+
+    <link href="<%=application.getContextPath()%>/lib/bootstrap/css/bootstrap.css" rel="stylesheet">
+    <link href="<%=application.getContextPath()%>/lib/font-awesome/css/font-awesome.css" rel="stylesheet">
+    <link href="<%=application.getContextPath()%>/css/reset.css" rel="stylesheet">
+    <link href="<%=application.getContextPath()%>/css/base.css" rel="stylesheet">
+</head>
+
+<body>
+
+<%
+    User client = (User)request.getAttribute("client");
+    List<Role> realmRolesRequested = (List<Role>)request.getAttribute("realmRolesRequested");
+    MultivaluedMap<String, Role> resourceRolesRequested = (MultivaluedMap<String, Role>)request.getAttribute("resourceRolesRequested");
+%>
+
+    <h1>Grant request for: <%=client.getLoginName()%></h1>
+<div class="modal-body">
+
+
+    <p>This app would like to:</p>
+    <hr/>
+    <%
+    if (realmRolesRequested.size() > 0) {
+       %> <ul> <%
+       for (Role role : realmRolesRequested) {
+          String desc = "Have " + role.getName() + " privileges.";
+          Attribute roleDesc = role.getAttribute("description");
+          if (roleDesc != null) {
+             desc = (String)roleDesc.getValue();
+          }
+          %>
+          <li><%=desc%></li>
+          <%
+       }
+       %> </ul> <%
+    }
+    for (String resource : resourceRolesRequested.keySet()) {
+       List<Role> roles = resourceRolesRequested.get(resource);
+       out.println("<i>For application " + resource + ":</i> ");
+       out.println("<ul>");
+       for (Role role : roles) {
+          String desc = "Have " + role.getName() + " privileges.";
+          Attribute roleDesc = role.getAttribute("description");
+          if (roleDesc != null) {
+             desc = (String)roleDesc.getValue();
+          }
+          out.println("<li>" + desc + "</li>");
+       }
+       out.println("</ul>");
+    }
+    %>
+    <hr/>
+
+
+    <form class="form-horizontal" name="oauthGrant" action="<%=request.getAttribute("action")%>" method="POST">
+       <input type="hidden" name="code" value="<%=request.getAttribute("code")%>">
+        <div class="control-group">
+            <div class="controls">
+                <input type="submit" name="accept" class="btn btn-primary" value="Accept">
+                <input type="submit" name="cancel" class="btn btn-primary" value="Cancel">
+            </div>
+        </div>
+    </form>
+</div>
+<footer>
+  <p>Powered By Keycloak</p>
+</body>
+</html>
diff --git a/examples/as7-eap-demo/third-party/pom.xml b/examples/as7-eap-demo/third-party/pom.xml
new file mode 100755
index 0000000..eedb752
--- /dev/null
+++ b/examples/as7-eap-demo/third-party/pom.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+        <artifactId>keycloak-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.0-alpha-1</version>
+        <relativePath>../../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.keycloak.example.as7.demo</groupId>
+    <artifactId>oauth-client-example</artifactId>
+    <packaging>war</packaging>
+    <name>Simple OAuth Client</name>
+    <description/>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.jboss.spec.javax.servlet</groupId>
+            <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+            <version>1.0.1.Final</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-client</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <finalName>oauth-client</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.jboss.as.plugins</groupId>
+                <artifactId>jboss-as-maven-plugin</artifactId>
+                <version>7.4.Final</version>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-deploy-plugin</artifactId>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.6</source>
+                    <target>1.6</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/examples/as7-eap-demo/third-party/src/main/java/org/jboss/resteasy/example/oauth/Bootstrap.java b/examples/as7-eap-demo/third-party/src/main/java/org/jboss/resteasy/example/oauth/Bootstrap.java
new file mode 100755
index 0000000..717cd3e
--- /dev/null
+++ b/examples/as7-eap-demo/third-party/src/main/java/org/jboss/resteasy/example/oauth/Bootstrap.java
@@ -0,0 +1,69 @@
+package org.jboss.resteasy.example.oauth;
+
+import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
+import org.keycloak.servlet.ServletOAuthClient;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import java.io.File;
+import java.io.FileInputStream;
+import java.security.KeyStore;
+
+/**
+ * Stupid init code to load up the truststore so we can make appropriate SSL connections
+ * You really should use a better way of initializing this stuff.
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class Bootstrap implements ServletContextListener {
+
+    private ServletOAuthClient client;
+
+    private static KeyStore loadKeyStore(String filename, String password) throws Exception {
+        KeyStore trustStore = KeyStore.getInstance(KeyStore
+                .getDefaultType());
+        File truststoreFile = new File(filename);
+        FileInputStream trustStream = new FileInputStream(truststoreFile);
+        trustStore.load(trustStream, password.toCharArray());
+        trustStream.close();
+        return trustStore;
+    }
+
+    @Override
+    public void contextInitialized(ServletContextEvent sce) {
+        client = new ServletOAuthClient();
+/*
+       // hardcoded, WARNING, you should really have a better way of doing this
+      // configuration.  Either use something like Spring or CDI, or even pull
+      // config vales from context-params
+      String truststorePath = "${jboss.server.config.dir}/client-truststore.ts";
+      String truststorePassword = "password";
+      truststorePath = EnvUtil.replace(truststorePath);
+      KeyStore truststore = null;
+      try
+      {
+         truststore = loadKeyStore(truststorePath, truststorePassword);
+      }
+      catch (Exception e)
+      {
+         throw new RuntimeException(e);
+      }
+      client.setTruststore(truststore);
+      */
+        client.setClientId("third-party");
+        client.setPassword("password");
+        client.setAuthUrl("http://localhost:8080/auth-server/rest/realms/demo/tokens/login");
+        client.setCodeUrl("http://localhost:8080/auth-server/rest/realms/demo/tokens/access/codes");
+        client.setClient(new ResteasyClientBuilder().build());
+        client.start();
+        sce.getServletContext().setAttribute(ServletOAuthClient.class.getName(), client);
+
+
+    }
+
+    @Override
+    public void contextDestroyed(ServletContextEvent sce) {
+        client.stop();
+    }
+}
diff --git a/examples/as7-eap-demo/third-party/src/main/java/org/jboss/resteasy/example/oauth/ProductDatabaseClient.java b/examples/as7-eap-demo/third-party/src/main/java/org/jboss/resteasy/example/oauth/ProductDatabaseClient.java
new file mode 100755
index 0000000..d21c823
--- /dev/null
+++ b/examples/as7-eap-demo/third-party/src/main/java/org/jboss/resteasy/example/oauth/ProductDatabaseClient.java
@@ -0,0 +1,69 @@
+package org.jboss.resteasy.example.oauth;
+
+import org.jboss.resteasy.client.jaxrs.ResteasyClient;
+import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
+import org.keycloak.servlet.ServletOAuthClient;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ProductDatabaseClient {
+    public static void redirect(HttpServletRequest request, HttpServletResponse response) {
+        // This is really the worst code ever. The ServletOAuthClient is obtained by getting a context attribute
+        // that is set in the Bootstrap context listenr in this project.
+        // You really should come up with a better way to initialize
+        // and obtain the ServletOAuthClient.  I actually suggest downloading the ServletOAuthClient code
+        // and take a look how it works.
+        ServletOAuthClient oAuthClient = (ServletOAuthClient) request.getServletContext().getAttribute(ServletOAuthClient.class.getName());
+        try {
+            oAuthClient.redirectRelative("pull_data.jsp", request, response);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static List<String> getProducts(HttpServletRequest request) {
+        // This is really the worst code ever. The ServletOAuthClient is obtained by getting a context attribute
+        // that is set in the Bootstrap context listenr in this project.
+        // You really should come up with a better way to initialize
+        // and obtain the ServletOAuthClient.  I actually suggest downloading the ServletOAuthClient code
+        // and take a look how it works.
+        ServletOAuthClient oAuthClient = (ServletOAuthClient) request.getServletContext().getAttribute(ServletOAuthClient.class.getName());
+        String token = oAuthClient.getBearerToken(request);
+        ResteasyClient client = new ResteasyClientBuilder()
+                .trustStore(oAuthClient.getTruststore())
+                .hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY).build();
+        try {
+            // invoke without the Authorization header
+            Response response = client.target("http://localhost:8080/database/products").request().get();
+            response.close();
+            if (response.getStatus() != 401) {
+                response.close();
+                client.close();
+                throw new RuntimeException("Expecting an auth status code: " + response.getStatus());
+            }
+        } finally {
+        }
+        try {
+            Response response = client.target("http://localhost:8080/database/products").request()
+                    .header(HttpHeaders.AUTHORIZATION, "Bearer " + token).get();
+            if (response.getStatus() != 200) {
+               response.close();
+               throw new RuntimeException("Failed to access!: " + response.getStatus());
+            }
+                return response.readEntity(new GenericType<List<String>>() {
+                });
+        } finally {
+            client.close();
+        }
+    }
+}
diff --git a/examples/as7-eap-demo/third-party/src/main/webapp/index.html b/examples/as7-eap-demo/third-party/src/main/webapp/index.html
new file mode 100644
index 0000000..dbd7d7a
--- /dev/null
+++ b/examples/as7-eap-demo/third-party/src/main/webapp/index.html
@@ -0,0 +1,6 @@
+<html>
+<body>
+<h1>Third Party App That Pulls Data Using OAuth</h1>
+<a href="redirect.jsp">Pull Data</a>
+</body>
+</html>
\ No newline at end of file
diff --git a/examples/as7-eap-demo/third-party/src/main/webapp/pull_data.jsp b/examples/as7-eap-demo/third-party/src/main/webapp/pull_data.jsp
new file mode 100644
index 0000000..63ad9d9
--- /dev/null
+++ b/examples/as7-eap-demo/third-party/src/main/webapp/pull_data.jsp
@@ -0,0 +1,21 @@
+<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
+ pageEncoding="ISO-8859-1"%>
+<html>
+<head>
+    <title>Pull Page</title>
+</head>
+<body>
+<h2>Pulled Product Listing</h2>
+<%
+java.util.List<String> list = org.jboss.resteasy.example.oauth.ProductDatabaseClient.getProducts(request);
+for (String prod : list)
+{
+   out.print("<p>");
+   out.print(prod);
+   out.println("</p>");
+
+}
+%>
+<br><br>
+</body>
+</html>
\ No newline at end of file
diff --git a/examples/as7-eap-demo/third-party/src/main/webapp/redirect.jsp b/examples/as7-eap-demo/third-party/src/main/webapp/redirect.jsp
new file mode 100644
index 0000000..35ff870
--- /dev/null
+++ b/examples/as7-eap-demo/third-party/src/main/webapp/redirect.jsp
@@ -0,0 +1,3 @@
+<%
+   org.jboss.resteasy.example.oauth.ProductDatabaseClient.redirect(request, response);
+%>
\ No newline at end of file
diff --git a/examples/as7-eap-demo/third-party/src/main/webapp/WEB-INF/jboss-deployment-structure.xml b/examples/as7-eap-demo/third-party/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
new file mode 100755
index 0000000..74f5dff
--- /dev/null
+++ b/examples/as7-eap-demo/third-party/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
@@ -0,0 +1,9 @@
+<jboss-deployment-structure>
+    <deployment>
+        <!-- This allows you to define additional dependencies, it is the same as using the Dependencies: manifest attribute -->
+        <dependencies>
+            <module name="org.jboss.resteasy.resteasy-jaxrs" services="import"/>
+            <module name="org.jboss.resteasy.resteasy-jackson-provider" services="import"/>
+        </dependencies>
+    </deployment>
+</jboss-deployment-structure>
\ No newline at end of file
diff --git a/examples/as7-eap-demo/third-party/src/main/webapp/WEB-INF/web.xml b/examples/as7-eap-demo/third-party/src/main/webapp/WEB-INF/web.xml
new file mode 100755
index 0000000..9273212
--- /dev/null
+++ b/examples/as7-eap-demo/third-party/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+      version="3.0">
+    <listener>
+        <listener-class>org.jboss.resteasy.example.oauth.Bootstrap</listener-class>
+    </listener>
+    <!--
+    <security-constraint>
+        <web-resource-collection>
+            <url-pattern>/*</url-pattern>
+        </web-resource-collection>
+        <user-data-constraint>
+            <transport-guarantee>CONFIDENTIAL</transport-guarantee>
+        </user-data-constraint>
+    </security-constraint>
+    -->
+
+</web-app>

examples/pom.xml 5(+1 -4)

diff --git a/examples/pom.xml b/examples/pom.xml
index 1fb476f..d491f21 100755
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -34,9 +34,6 @@
         </plugins>
     </build>
     <modules>
-        <module>as7-eap-demo/server</module>
-        <module>as7-eap-demo/customer-app</module>
-        <module>as7-eap-demo/product-app</module>
-        <module>as7-eap-demo/database-service</module>
+        <module>as7-eap-demo</module>
     </modules>
 </project>
diff --git a/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java b/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java
index d96b32b..bbffedf 100755
--- a/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java
+++ b/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java
@@ -1,8 +1,13 @@
 package org.keycloak.services.managers;
 
 import org.keycloak.representations.SkeletonKeyToken;
+import org.picketlink.idm.model.Role;
 import org.picketlink.idm.model.User;
 
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.UUID;
 
 /**
@@ -11,9 +16,16 @@ import java.util.UUID;
 */
 public class AccessCodeEntry {
     protected String id = UUID.randomUUID().toString() + System.currentTimeMillis();
+    protected String code;
+    protected String state;
+    protected String redirectUri;
+
     protected long expiration;
     protected SkeletonKeyToken token;
+    protected User user;
     protected User client;
+    protected List<Role> realmRolesRequested = new ArrayList<Role>();
+    MultivaluedMap<String, Role> resourceRolesRequested = new MultivaluedHashMap<String, Role>();
 
     public boolean isExpired() {
         return expiration != 0 && (System.currentTimeMillis() / 1000) > expiration;
@@ -23,6 +35,14 @@ public class AccessCodeEntry {
         return id;
     }
 
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
     public long getExpiration() {
         return expiration;
     }
@@ -46,4 +66,36 @@ public class AccessCodeEntry {
     public void setClient(User client) {
         this.client = client;
     }
+
+    public User getUser() {
+        return user;
+    }
+
+    public void setUser(User user) {
+        this.user = user;
+    }
+
+    public List<Role> getRealmRolesRequested() {
+        return realmRolesRequested;
+    }
+
+    public MultivaluedMap<String, Role> getResourceRolesRequested() {
+        return resourceRolesRequested;
+    }
+
+    public String getState() {
+        return state;
+    }
+
+    public void setState(String state) {
+        this.state = state;
+    }
+
+    public String getRedirectUri() {
+        return redirectUri;
+    }
+
+    public void setRedirectUri(String redirectUri) {
+        this.redirectUri = redirectUri;
+    }
 }
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index 080e688..d2a512e 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -5,6 +5,7 @@ import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.RequiredCredentialRepresentation;
 import org.keycloak.representations.idm.ResourceRepresentation;
 import org.keycloak.representations.idm.RoleMappingRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
 import org.keycloak.representations.idm.ScopeMappingRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.services.models.RealmModel;
@@ -23,6 +24,7 @@ import org.picketlink.idm.model.User;
 
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.Response;
+import java.io.Serializable;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.NoSuchAlgorithmException;
@@ -38,6 +40,9 @@ import java.util.concurrent.atomic.AtomicLong;
  */
 public class RealmManager {
     private static AtomicLong counter = new AtomicLong(1);
+    public static final String RESOURCE_ROLE = "KEYCLOAK_RESOURCE";
+    public static final String IDENTITY_REQUESTER_ROLE = "KEYCLOAK_IDENTITY_REQUESTER";
+    public static final String WILDCARD_ROLE = "*";
 
     public static String generateId() {
         return counter.getAndIncrement() + "-" + System.currentTimeMillis();
@@ -71,7 +76,9 @@ public class RealmManager {
         SimpleAgent agent = new SimpleAgent(RealmModel.REALM_AGENT_ID);
         idm.add(agent);
         RealmModel realm = new RealmModel(newRealm, identitySession);
-        idm.add(new SimpleRole("*"));
+        idm.add(new SimpleRole(WILDCARD_ROLE));
+        idm.add(new SimpleRole(RESOURCE_ROLE));
+        idm.add(new SimpleRole(IDENTITY_REQUESTER_ROLE));
         return realm;
     }
 
@@ -145,8 +152,9 @@ public class RealmManager {
         }
 
         if (rep.getRoles() != null) {
-            for (String roleString : rep.getRoles()) {
-                SimpleRole role = new SimpleRole(roleString.trim());
+            for (RoleRepresentation roleRep : rep.getRoles()) {
+                SimpleRole role = new SimpleRole(roleRep.getName());
+                if (roleRep.getDescription() != null) role.setAttribute(new Attribute<String>("description", roleRep.getDescription()));
                 newRealm.getIdm().add(role);
             }
         }
@@ -186,6 +194,7 @@ public class RealmManager {
     }
 
     protected void createResources(RealmRepresentation rep, RealmModel realm, Map<String, User> userMap) {
+        Role loginRole = realm.getIdm().getRole(RealmManager.RESOURCE_ROLE);
         for (ResourceRepresentation resourceRep : rep.getResources()) {
             ResourceModel resource = realm.addResource(resourceRep.getName());
             resource.setManagementUrl(resourceRep.getAdminUrl());
@@ -202,11 +211,13 @@ public class RealmManager {
                 }
             }
             userMap.put(resourceUser.getLoginName(), resourceUser);
+            realm.getIdm().grantRole(resourceUser, loginRole);
 
 
             if (resourceRep.getRoles() != null) {
-                for (String roleString : resourceRep.getRoles()) {
-                    SimpleRole role = new SimpleRole(roleString.trim());
+                for (RoleRepresentation roleRep : resourceRep.getRoles()) {
+                    SimpleRole role = new SimpleRole(roleRep.getName());
+                    if (roleRep.getDescription() != null) role.setAttribute(new Attribute<String>("description", roleRep.getDescription()));
                     resource.getIdm().add(role);
                 }
             }
diff --git a/services/src/main/java/org/keycloak/services/managers/TokenManager.java b/services/src/main/java/org/keycloak/services/managers/TokenManager.java
index 68ef550..a50fbff 100755
--- a/services/src/main/java/org/keycloak/services/managers/TokenManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/TokenManager.java
@@ -3,19 +3,16 @@ package org.keycloak.services.managers;
 import org.jboss.resteasy.jose.Base64Url;
 import org.jboss.resteasy.jose.jws.JWSBuilder;
 import org.jboss.resteasy.jwt.JsonSerialization;
-import org.jboss.resteasy.spi.HttpResponse;
-import org.jboss.resteasy.spi.ResteasyProviderFactory;
 import org.keycloak.representations.SkeletonKeyScope;
 import org.keycloak.representations.SkeletonKeyToken;
 import org.keycloak.services.models.RealmModel;
 import org.keycloak.services.models.ResourceModel;
 import org.keycloak.services.resources.RealmsResource;
+import org.picketlink.idm.model.Role;
 import org.picketlink.idm.model.User;
 
-import javax.ws.rs.ForbiddenException;
-import javax.ws.rs.core.Cookie;
+import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.NewCookie;
-import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
@@ -40,6 +37,9 @@ public class TokenManager {
         accessCodeMap.clear();
     }
 
+    public AccessCodeEntry getAccessCode(String key) {
+        return accessCodeMap.get(key);
+    }
 
     public AccessCodeEntry pullAccessCode(String key) {
         return accessCodeMap.remove(key);
@@ -54,16 +54,59 @@ public class TokenManager {
         return cookie;
     }
 
-    public String createAccessCode(String scopeParam, RealmModel realm, User client, User user)
-    {
-        SkeletonKeyToken token = null;
-        if (scopeParam != null) token = createScopedToken(scopeParam, realm, client, user);
-        else token = createUnscopedToken(realm, client, user);
-
+    public AccessCodeEntry createAccessCode(String scopeParam, String state, String redirect, RealmModel realm, User client, User user) {
         AccessCodeEntry code = new AccessCodeEntry();
+        SkeletonKeyScope scopeMap = null;
+        if (scopeParam != null) scopeMap = decodeScope(scopeParam);
+        List<Role> realmRolesRequested = code.getRealmRolesRequested();
+        MultivaluedMap<String, Role> resourceRolesRequested = code.getResourceRolesRequested();
+        Set<String> realmMapping = realm.getRoleMappings(user);
+
+        if (realmMapping != null && realmMapping.size() > 0 && (scopeMap == null || scopeMap.containsKey("realm"))) {
+            Set<String> scope = realm.getScope(client);
+            if (scope.size() > 0) {
+                Set<String> scopeRequest = null;
+                if (scopeMap != null) {
+                    scopeRequest.addAll(scopeMap.get("realm"));
+                    if (scopeRequest.contains(RealmManager.WILDCARD_ROLE)) scopeRequest = null;
+                }
+                for (String role : realmMapping) {
+                    if (
+                            (scopeRequest == null || scopeRequest.contains(role)) &&
+                                    (scope.contains("*") || scope.contains(role))
+                            )
+                        realmRolesRequested.add(realm.getIdm().getRole(role));
+                }
+            }
+        }
+        for (ResourceModel resource : realm.getResources()) {
+            Set<String> mapping = resource.getRoleMappings(user);
+            if (mapping != null && mapping.size() > 0 && (scopeMap == null || scopeMap.containsKey(resource.getName()))) {
+                Set<String> scope = resource.getScope(client);
+                if (scope.size() > 0) {
+                    Set<String> scopeRequest = null;
+                    if (scopeMap != null) {
+                        scopeRequest.addAll(scopeMap.get(resource.getName()));
+                        if (scopeRequest.contains(RealmManager.WILDCARD_ROLE)) scopeRequest = null;
+                    }
+                    for (String role : mapping) {
+                        if (
+                                (scopeRequest == null || scopeRequest.contains(role)) &&
+                                        (scope.contains("*") || scope.contains(role))
+                                )
+                            resourceRolesRequested.add(resource.getName(), resource.getIdm().getRole(role));
+                    }
+                }
+            }
+        }
+
+
+        createToken(code, realm, client, user);
         code.setExpiration((System.currentTimeMillis() / 1000) + realm.getAccessCodeLifespan());
-        code.setToken(token);
         code.setClient(client);
+        code.setUser(user);
+        code.setState(state);
+        code.setRedirectUri(redirect);
         accessCodeMap.put(code.getId(), code);
         String accessCode = null;
         try {
@@ -71,30 +114,8 @@ public class TokenManager {
         } catch (UnsupportedEncodingException e) {
             throw new RuntimeException(e);
         }
-        return accessCode;
-    }
-
-    public SkeletonKeyToken createScopedToken(SkeletonKeyScope scope, RealmModel realm, User client, User user) {
-        SkeletonKeyToken token = initToken(realm, client, user);
-        Map<String, ResourceModel> resourceMap = realm.getResourceMap();
-
-        for (String res : scope.keySet()) {
-            ResourceModel resource = resourceMap.get(res);
-            Set<String> scopeMapping = resource.getScope(client);
-            Set<String> roleMapping = resource.getRoleMappings(user);
-            SkeletonKeyToken.Access access = token.addAccess(resource.getName());
-            for (String role : scope.get(res)) {
-                if (!scopeMapping.contains("*") && !scopeMapping.contains(role)) {
-                    throw new ForbiddenException(Response.status(403).entity("<h1>Security Alert</h1><p>Known client not authorized for the requested scope.</p>").type("text/html").build());
-                }
-                if (!roleMapping.contains(role)) {
-                    throw new ForbiddenException(Response.status(403).entity("<h1>Security Alert</h1><p>Known client not authorized for the requested scope.</p>").type("text/html").build());
-
-                }
-                access.addRole(role);
-            }
-        }
-        return token;
+        code.setCode(accessCode);
+        return code;
     }
 
     protected SkeletonKeyToken initToken(RealmModel realm, User client, User user) {
@@ -110,38 +131,29 @@ public class TokenManager {
         return token;
     }
 
-    public SkeletonKeyToken createScopedToken(String scopeParam, RealmModel realm, User client, User user) {
-        SkeletonKeyScope scope = decodeScope(scopeParam);
-        return createScopedToken(scope, realm, client, user);
-    }
-
-    public SkeletonKeyToken createUnscopedToken(RealmModel realm, User client, User user) {
+    protected void createToken(AccessCodeEntry accessCodeEntry, RealmModel realm, User client, User user) {
 
         SkeletonKeyToken token = initToken(realm, client, user);
 
-        Set<String> realmMapping = realm.getRoleMappings(user);
-
-        if (realmMapping != null && realmMapping.size() > 0) {
-            Set<String> scope = realm.getScope(client);
+        if (accessCodeEntry.getRealmRolesRequested().size() > 0) {
             SkeletonKeyToken.Access access = new SkeletonKeyToken.Access();
-            for (String role : realmMapping) {
-                if (scope.contains("*") || scope.contains(role)) access.addRole(role);
+            for (Role role : accessCodeEntry.getRealmRolesRequested()) {
+                access.addRole(role.getName());
             }
             token.setRealmAccess(access);
         }
-        List<ResourceModel> resources = realm.getResources();
-        for (ResourceModel resource : resources) {
-            Set<String> scope = resource.getScope(client);
-            Set<String> mapping = resource.getRoleMappings(user);
-            if (mapping.size() == 0 || scope.size() == 0) continue;
-            SkeletonKeyToken.Access access = token.addAccess(resource.getName())
-                    .verifyCaller(resource.isSurrogateAuthRequired());
-            for (String role : mapping) {
-                if (scope.contains("*") || scope.contains(role)) access.addRole(role);
+
+        if (accessCodeEntry.getResourceRolesRequested().size() > 0) {
+            Map<String, ResourceModel> resourceMap = realm.getResourceMap();
+            for (String resourceName : accessCodeEntry.getResourceRolesRequested().keySet()) {
+                ResourceModel resource = resourceMap.get(resourceName);
+                SkeletonKeyToken.Access access = token.addAccess(resourceName).verifyCaller(resource.isSurrogateAuthRequired());
+                for (Role role : accessCodeEntry.getResourceRolesRequested().get(resourceName)) {
+                    access.addRole(role.getName());
+                }
             }
         }
-        return token;
-
+        accessCodeEntry.setToken(token);
     }
 
     public String encodeScope(SkeletonKeyScope scope) {
diff --git a/services/src/main/java/org/keycloak/services/models/RealmModel.java b/services/src/main/java/org/keycloak/services/models/RealmModel.java
index 6ab850a..171e57d 100755
--- a/services/src/main/java/org/keycloak/services/models/RealmModel.java
+++ b/services/src/main/java/org/keycloak/services/models/RealmModel.java
@@ -38,6 +38,8 @@ import java.util.Map;
 import java.util.Set;
 
 /**
+ * Meant to be a per-request object
+ *
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
@@ -57,6 +59,7 @@ public class RealmModel {
     protected IdentitySession identitySession;
     protected volatile transient PublicKey publicKey;
     protected volatile transient PrivateKey privateKey;
+    protected IdentityManager idm;
 
     public RealmModel(Realm realm, IdentitySession session) {
         this.realm = realm;
@@ -65,7 +68,8 @@ public class RealmModel {
     }
 
     public IdentityManager getIdm() {
-        return identitySession.createIdentityManager(realm);
+        if (idm == null) idm = identitySession.createIdentityManager(realm);
+        return idm;
     }
 
     public void updateRealm() {
diff --git a/services/src/main/java/org/keycloak/services/models/ResourceModel.java b/services/src/main/java/org/keycloak/services/models/ResourceModel.java
index c47785b..f9b5ca3 100755
--- a/services/src/main/java/org/keycloak/services/models/ResourceModel.java
+++ b/services/src/main/java/org/keycloak/services/models/ResourceModel.java
@@ -25,6 +25,7 @@ public class ResourceModel {
     protected ResourceRelationship agent;
     protected RealmModel realm;
     protected IdentitySession identitySession;
+    protected IdentityManager idm;
 
     public ResourceModel(Tier tier, ResourceRelationship agent, RealmModel realm, IdentitySession session) {
         this.tier = tier;
@@ -34,7 +35,8 @@ public class ResourceModel {
     }
 
     public IdentityManager getIdm() {
-        return identitySession.createIdentityManager(tier);
+        if (idm == null) idm = identitySession.createIdentityManager(tier);
+        return idm;
     }
 
     public void updateResource() {
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 eaaaf74..8fc87af 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -1,24 +1,23 @@
 package org.keycloak.services.resources;
 
-import org.jboss.resteasy.jose.Base64Url;
 import org.jboss.resteasy.jose.jws.JWSBuilder;
 import org.jboss.resteasy.jose.jws.JWSInput;
 import org.jboss.resteasy.jose.jws.crypto.RSAProvider;
 import org.jboss.resteasy.jwt.JsonSerialization;
 import org.jboss.resteasy.logging.Logger;
 import org.jboss.resteasy.spi.HttpRequest;
+import org.jboss.resteasy.spi.HttpResponse;
 import org.keycloak.representations.AccessTokenResponse;
-import org.keycloak.representations.SkeletonKeyScope;
 import org.keycloak.representations.SkeletonKeyToken;
 import org.keycloak.services.JspRequestParameters;
 import org.keycloak.services.managers.AccessCodeEntry;
 import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.managers.ResourceAdminManager;
 import org.keycloak.services.managers.TokenManager;
 import org.keycloak.services.models.RealmModel;
-import org.keycloak.services.models.RequiredCredentialModel;
-import org.keycloak.services.models.ResourceModel;
 import org.picketlink.idm.IdentitySession;
+import org.picketlink.idm.model.Role;
 import org.picketlink.idm.model.User;
 
 import javax.ws.rs.Consumes;
@@ -39,9 +38,7 @@ import javax.ws.rs.core.UriInfo;
 import javax.ws.rs.ext.Providers;
 import java.security.PrivateKey;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -64,10 +61,13 @@ public class TokenService {
     protected IdentitySession identitySession;
     @Context
     HttpRequest request;
+    @Context
+    HttpResponse response;
 
 
     protected String securityFailurePath = "/securityFailure.jsp";
     protected String loginFormPath = "/loginForm.jsp";
+    protected String oauthFormPath = "/oauthGrantForm.jsp";
 
     protected RealmModel realm;
     protected TokenManager tokenManager;
@@ -108,6 +108,10 @@ public class TokenService {
         return tokenServiceBaseUrl(uriInfo).path(TokenService.class, "processLogin");
     }
 
+    public static UriBuilder processOAuthUrl(UriInfo uriInfo) {
+        return tokenServiceBaseUrl(uriInfo).path(TokenService.class, "processOAuth");
+    }
+
 
     @Path("grants/identity-token")
     @POST
@@ -209,16 +213,37 @@ public class TokenService {
             return null;
         }
 
-        return redirectAccessCode(scopeParam, state, redirect, client, user);
+        return processAccessCode(scopeParam, state, redirect, client, user);
+    }
+
+    protected Response processAccessCode(String scopeParam, String state, String redirect, User client, User user) {
+        Role resourceRole = realm.getIdm().getRole(RealmManager.RESOURCE_ROLE);
+        Role identityRequestRole = realm.getIdm().getRole(RealmManager.IDENTITY_REQUESTER_ROLE);
+        boolean isResource = realm.getIdm().hasRole(client, resourceRole);
+        if (!isResource && !realm.getIdm().hasRole(client, identityRequestRole)) {
+            securityFailureForward("Login requester not allowed to request login.");
+            identitySession.close();
+            return null;
+        }
+        AccessCodeEntry accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, realm, client, user);
+        logger.info("processAccessCode: isResource: " + isResource);
+        logger.info("processAccessCode: go to oauth page?: " + (!isResource && (accessCode.getRealmRolesRequested().size() > 0 || accessCode.getResourceRolesRequested().size() > 0)));
+        if (!isResource && (accessCode.getRealmRolesRequested().size() > 0 || accessCode.getResourceRolesRequested().size() > 0)) {
+            oauthGrantPage(accessCode, client);
+            identitySession.close();
+            return null;
+        }
+        return redirectAccessCode(accessCode, state, redirect);
     }
 
-    protected Response redirectAccessCode(String scopeParam, String state, String redirect, User client, User user) {
-        String accessCode = tokenManager.createAccessCode(scopeParam, realm, client, user);
-        UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam("code", accessCode);
+    protected Response redirectAccessCode(AccessCodeEntry accessCode, String state, String redirect) {
+        String code = accessCode.getCode();
+        UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam("code", code);
+        logger.info("redirectAccessCode: state: " + state);
         if (state != null) redirectUri.queryParam("state", state);
         Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
         if (realm.isCookieLoginAllowed()) {
-           location.cookie(tokenManager.createLoginCookie(realm, user, uriInfo));
+            location.cookie(tokenManager.createLoginCookie(realm, accessCode.getUser(), uriInfo));
         }
         return location.build();
     }
@@ -390,11 +415,20 @@ public class TokenService {
             return null;
         }
 
+        Role resourceRole = realm.getIdm().getRole(RealmManager.RESOURCE_ROLE);
+        Role identityRequestRole = realm.getIdm().getRole(RealmManager.IDENTITY_REQUESTER_ROLE);
+        boolean isResource = realm.getIdm().hasRole(client, resourceRole);
+        if (!isResource && !realm.getIdm().hasRole(client, identityRequestRole)) {
+            securityFailureForward("Login requester not allowed to request login.");
+            identitySession.close();
+            return null;
+        }
+
         User user = authManager.authenticateIdentityCookie(realm, uriInfo, headers);
         if (user != null) {
-            return redirectAccessCode(scopeParam, state, redirect, client, user);
+            logger.info(user.getLoginName() + " already logged in.");
+            return processAccessCode(scopeParam, state, redirect, client, user);
         }
-        // todo make sure client is allowed to request a login
 
         forwardToLoginForm(redirect, clientId, scopeParam, state);
         return null;
@@ -415,117 +449,56 @@ public class TokenService {
         return Response.status(302).location(UriBuilder.fromUri(redirectUri).build()).build();
     }
 
-    private Response loginForm(String validationError, String redirect, String clientId, String scopeParam, String state, RealmModel realm, User client) {
-        StringBuffer html = new StringBuffer();
-        if (scopeParam != null) {
-            html.append("<h1>Grant Request For ").append(realm.getName()).append(" Realm</h1>");
-            if (validationError != null) {
-                try {
-                    Thread.sleep(1000); // put in a delay
-                } catch (InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-                html.append("<p/><p><b>").append(validationError).append("</b></p>");
-            }
-            html.append("<p>A Third Party is requesting access to the following resources</p>");
-            html.append("<table>");
-            SkeletonKeyScope scope = tokenManager.decodeScope(scopeParam);
-            Map<String, ResourceModel> resourceMap = realm.getResourceMap();
-
-            for (String res : scope.keySet()) {
-                ResourceModel resource = resourceMap.get(res);
-                html.append("<tr><td><b>Resource: </b>").append(resource.getName()).append("</td><td><b>Roles:</b>");
-                Set<String> scopeMapping = resource.getScope(client);
-                for (String role : scope.get(res)) {
-                    html.append(" ").append(role);
-                    if (!scopeMapping.contains("*") && !scopeMapping.contains(role)) {
-                        return Response.ok("<h1>Security Alert</h1><p>Known client not authorized for the requested scope.</p>").type("text/html").build();
-                    }
-                }
-                html.append("</td></tr>");
-            }
-            html.append("</table><p>To Authorize, please login below</p>");
-        } else {
-            Set<String> scopeMapping = realm.getScope(client);
-            if (scopeMapping.contains("*")) {
-                html.append("<h1>Login For ").append(realm.getName()).append(" Realm</h1>");
-                if (validationError != null) {
-                    try {
-                        Thread.sleep(1000); // put in a delay
-                    } catch (InterruptedException e) {
-                        throw new RuntimeException(e);
-                    }
-                    html.append("<p/><p><b>").append(validationError).append("</b></p>");
-                }
-            } else {
-                html.append("<h1>Grant Request For ").append(realm.getName()).append(" Realm</h1>");
-                if (validationError != null) {
-                    try {
-                        Thread.sleep(1000); // put in a delay
-                    } catch (InterruptedException e) {
-                        throw new RuntimeException(e);
-                    }
-                    html.append("<p/><p><b>").append(validationError).append("</b></p>");
-                }
-                SkeletonKeyScope scope = new SkeletonKeyScope();
-                List<ResourceModel> resources = realm.getResources();
-                boolean found = false;
-                for (ResourceModel resource : resources) {
-                    Set<String> resourceScope = resource.getScope(client);
-                    if (resourceScope == null) continue;
-                    if (resourceScope.size() == 0) continue;
-                    if (!found) {
-                        found = true;
-                        html.append("<p>A Third Party is requesting access to the following resources</p>");
-                        html.append("<table>");
-                    }
-                    html.append("<tr><td><b>Resource: </b>").append(resource.getName()).append("</td><td><b>Roles:</b>");
-                    // todo add description of role
-                    for (String role : resourceScope) {
-                        html.append(" ").append(role);
-                        scope.add(resource.getName(), role);
-                    }
-                }
-                if (!found) {
-                    return Response.ok("<h1>Security Alert</h1><p>Known client not authorized to access this realm.</p>").type("text/html").build();
-                }
-                html.append("</table>");
-                try {
-                    String json = JsonSerialization.toString(scope, false);
-                    scopeParam = Base64Url.encode(json.getBytes("UTF-8"));
-                } catch (Exception e) {
-                    throw new RuntimeException(e);
-                }
-
-            }
-        }
-
-        UriBuilder formActionUri = processLoginUrl(uriInfo);
-        String action = formActionUri.build(realm.getId()).toString();
-        html.append("<form action=\"").append(action).append("\" method=\"POST\">");
-        html.append("Username: <input type=\"text\" name=\"username\" size=\"20\"><br>");
-
-        for (RequiredCredentialModel credential : realm.getRequiredCredentials()) {
-            if (!credential.isInput()) continue;
-            html.append(credential.getType()).append(": ");
-            if (credential.isSecret()) {
-                html.append("<input type=\"password\" name=\"").append(credential.getType()).append("\"  size=\"20\"><br>");
-
-            } else {
-                html.append("<input type=\"text\" name=\"").append(credential.getType()).append("\"  size=\"20\"><br>");
-            }
-        }
-        html.append("<input type=\"hidden\" name=\"client_id\" value=\"").append(clientId).append("\">");
-        if (scopeParam != null) {
-            html.append("<input type=\"hidden\" name=\"scope\" value=\"").append(scopeParam).append("\">");
-        }
-        if (state != null) html.append("<input type=\"hidden\" name=\"state\" value=\"").append(state).append("\">");
-        html.append("<input type=\"hidden\" name=\"redirect_uri\" value=\"").append(redirect).append("\">");
-        html.append("<input type=\"submit\" value=\"");
-        if (scopeParam == null) html.append("Login");
-        else html.append("Grant Access");
-        html.append("\">");
-        html.append("</form>");
-        return Response.ok(html.toString()).type("text/html").build();
+    @Path("oauth/grant")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response processOAuth(MultivaluedMap<String, String> formData) {
+        String code = formData.getFirst("code");
+        JWSInput input = new JWSInput(code, providers);
+        boolean verifiedCode = false;
+        try {
+            verifiedCode = RSAProvider.verify(input, realm.getPublicKey());
+        } catch (Exception ignored) {
+            logger.debug("Failed to verify signature", ignored);
+        }
+        if (!verifiedCode) {
+            securityFailureForward("Illegal access code.");
+            identitySession.close();
+            return null;
+        }
+        String key = input.readContent(String.class);
+        AccessCodeEntry accessCodeEntry = tokenManager.getAccessCode(key);
+        if (accessCodeEntry == null) {
+            securityFailureForward("Unknown access code.");
+            identitySession.close();
+            return null;
+        }
+
+        String redirect = accessCodeEntry.getRedirectUri();
+        String state = accessCodeEntry.getState();
+
+        if (formData.containsKey("cancel")) {
+            return redirectAccessDenied(redirect, state);
+        }
+
+        return redirectAccessCode(accessCodeEntry, state, redirect);
+    }
+
+    protected Response redirectAccessDenied(String redirect, String state) {
+        UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam("error", "access_denied");
+        if (state != null) redirectUri.queryParam("state", state);
+        Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
+        return location.build();
     }
+
+    protected void oauthGrantPage(AccessCodeEntry accessCode, User client) {
+        request.setAttribute("realmRolesRequested", accessCode.getRealmRolesRequested());
+        request.setAttribute("resourceRolesRequested", accessCode.getResourceRolesRequested());
+        request.setAttribute("client", client);
+        request.setAttribute("action", processOAuthUrl(uriInfo).build(realm.getId()).toString());
+        request.setAttribute("code", accessCode.getCode());
+
+        request.forward(oauthFormPath);
+    }
+
 }
diff --git a/services/src/test/java/org/keycloak/test/AdapterTest.java b/services/src/test/java/org/keycloak/test/AdapterTest.java
index f798539..1bcbca6 100755
--- a/services/src/test/java/org/keycloak/test/AdapterTest.java
+++ b/services/src/test/java/org/keycloak/test/AdapterTest.java
@@ -166,7 +166,7 @@ public class AdapterTest {
         idm.add(new SimpleRole("admin"));
         idm.add(new SimpleRole("user"));
         List<Role> roles = realmModel.getRoles();
-        Assert.assertEquals(3, roles.size());
+        Assert.assertEquals(5, roles.size());
         SimpleUser user = new SimpleUser("bburke");
         idm.add(user);
         Role role = idm.getRole("user");
diff --git a/services/src/test/resources/testrealm.json b/services/src/test/resources/testrealm.json
index b58bdd8..a8d0cbf 100755
--- a/services/src/test/resources/testrealm.json
+++ b/services/src/test/resources/testrealm.json
@@ -62,7 +62,10 @@
    "resources" : [
        {
           "name" : "Application",
-          "roles" : ["admin", "user"],
+          "roles" : [
+              { "name" : "admin" },
+              { "name" : "user" }
+          ],
           "roleMappings" : [
              {
                 "username" : "wburke",
@@ -82,7 +85,10 @@
        },
               {
                  "name" : "OtherApp",
-                 "roles" : ["admin", "user"],
+          "roles" : [
+              { "name" : "admin" },
+              { "name" : "user" }
+          ],
                  "roleMappings" : [
                     {
                        "username" : "wburke",