<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Rbac on Janik von Rotz</title>
    <link>https://janikvonrotz.ch/tags/rbac/</link>
    <description>Recent content in Rbac on Janik von Rotz</description>
    <generator>Hugo</generator>
    <language>en</language>
    <lastBuildDate>Thu, 30 Apr 2020 22:38:57 +0200</lastBuildDate>
    <atom:link href="https://janikvonrotz.ch/tags/rbac/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Role based access control for multiple Keycloak clients</title>
      <link>https://janikvonrotz.ch/2020/04/30/role-based-access-control-for-multiple-keycloak-clients/</link>
      <pubDate>Thu, 30 Apr 2020 22:38:57 +0200</pubDate>
      <guid>https://janikvonrotz.ch/2020/04/30/role-based-access-control-for-multiple-keycloak-clients/</guid>
      <description>&lt;p&gt;Role based access control (RBAC) is a common feature in identity and access management (IAM) systems. Granting access to applications by assigning roles to a selection of users is the proper way to manage access permissions.&lt;/p&gt;&#xA;&lt;p&gt;In this guide I will show you how this can be implemented with Keycloak. We will create a authentication flow that checks if a user is eligible to access the client. This authentication flow can be applied to any Keycloak client.&lt;/p&gt;&#xA;&lt;p&gt;Lets assume that we have set up the following Keycloak resources:&lt;/p&gt;&#xA;&lt;p&gt;Keycloak realm: example.com&lt;br&gt;&#xA;Openid-connect client: app.example.com&lt;/p&gt;&#xA;&lt;p&gt;Now are going implement a custom authentication flow script. Therefore we have to enable the script feature for Keycloak.&lt;/p&gt;&#xA;&lt;h1 id=&#34;enable-scripts-for-keycloak&#34;&gt;Enable scripts for Keycloak&lt;/h1&gt;&#xA;&lt;p&gt;Run the Keycloak server with the following option:&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;-Dkeycloak.profile.feature.upload_scripts=enabled&lt;/code&gt;&lt;/p&gt;&#xA;&lt;p&gt;If you run Keycloak with Docker you can pass the option as command argument.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;docker run -p 8080:8080 jboss/keycloak -Dkeycloak.profile.feature.upload_scripts=enabled&lt;/code&gt;&lt;/p&gt;&#xA;&lt;p&gt;In the log you should see these entries:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;11:33:23,596 WARN  [org.keycloak.common.Profile] (ServerService Thread Pool -- 62) Deprecated feature enabled: upload_scripts&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;11:33:23,598 WARN  [org.keycloak.common.Profile] (ServerService Thread Pool -- 62) Preview feature enabled: scripts&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next we are going to create the client role.&lt;/p&gt;&#xA;&lt;h1 id=&#34;create-client-role&#34;&gt;Create client role&lt;/h1&gt;&#xA;&lt;p&gt;In your realm navigate to &lt;em&gt;Configure &amp;gt; Clients &amp;gt; app.example.com &amp;gt; Roles &amp;gt; Add Role&lt;/em&gt;. Define &lt;code&gt;access&lt;/code&gt; as the role name.&lt;/p&gt;&#xA;&lt;p&gt;Assign this role to users which should be allowed to access the client.&lt;/p&gt;&#xA;&lt;p&gt;Now it is time to update the authentication flow.&lt;/p&gt;&#xA;&lt;h1 id=&#34;keycloak-flow&#34;&gt;Keycloak flow&lt;/h1&gt;&#xA;&lt;p&gt;Navigate to &lt;em&gt;Configure &amp;gt; Authentication &amp;gt; Flow&lt;/em&gt; and select the &lt;em&gt;Browser&lt;/em&gt; flow. Make a copy of the flow and set as &lt;em&gt;New Name&lt;/em&gt; &lt;code&gt;browser role access control&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;At the end of the &lt;em&gt;Browser Role Access Control Forms&lt;/em&gt; row click on &lt;em&gt;Actions &amp;gt; Add execution&lt;/em&gt;. Select &lt;em&gt;Script&lt;/em&gt; from the provider list and hit save. Move the new entry with the arrow button up until it is below the &lt;em&gt;Username Password Form&lt;/em&gt; row. Then set the requirement to &lt;em&gt;REQUIRED&lt;/em&gt; for the Script row.&lt;/p&gt;&#xA;&lt;p&gt;Now we will add the script that checks if a user has a client role. Click on &lt;em&gt;Actions &amp;gt; Config&lt;/em&gt; at the end of the Script row. Update the script as showed below.&lt;/p&gt;&#xA;&lt;p&gt;Alias: &lt;code&gt;hasClientRole&lt;/code&gt;&lt;br&gt;&#xA;Script Name: &lt;code&gt;hasClientRole&lt;/code&gt;&lt;br&gt;&#xA;Script Source:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/*&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * Template for JavaScript based authenticator&amp;#39;s.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * See org.keycloak.authentication.authenticators.browser.ScriptBasedAuthenticatorFactory&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; */&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;FormMessage&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Java&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;org.keycloak.models.utils.FormMessage&amp;#39;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/**&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * An example authenticate function.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; *&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * The following variables are available for convenience:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * user - current user {@see org.keycloak.models.UserModel}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * realm - current realm {@see org.keycloak.models.RealmModel}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * session - current KeycloakSession {@see org.keycloak.models.KeycloakSession}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * httpRequest - current HttpRequest {@see org.jboss.resteasy.spi.HttpRequest}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * script - current script {@see org.keycloak.models.ScriptModel}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * authenticationSession - current authentication session {@see org.keycloak.sessions.AuthenticationSessionModel}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * LOG - current logger {@see org.jboss.logging.Logger}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; *&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * You one can extract current http request headers via:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * httpRequest.getHttpHeaders().getHeaderString(&amp;#34;Forwarded&amp;#34;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; *&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @param context {@see org.keycloak.authentication.AuthenticationFlowContext}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; */&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;authenticate&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;username&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;username&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;anonymous&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;LOG&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;info&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;script&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34; trace auth for: &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;username&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;session&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getContext&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;getClient&lt;/span&gt;();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;//var rolesClient = user.getClientRoleMappings(client);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;roleModel&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getRole&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;access&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// if (rolesClient.isEmpty()) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;hasRole&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;roleModel&lt;/span&gt;)) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;forkWithErrorMessage&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FormMessage&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;User is not allowed to access this client.&amp;#39;&lt;/span&gt;));&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;success&lt;/span&gt;();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Update 1:&lt;/strong&gt; The script has been updated to check if user actually has the role &lt;em&gt;access&lt;/em&gt; and not only any role of the client. This change was proposed in the comments by @rogier.&lt;/p&gt;&#xA;&lt;p&gt;The script retrieves the roles mappings of the current client from the session context and if the user has not assigned any roles it will deny access.&lt;/p&gt;&#xA;&lt;p&gt;Just to make sure that the flow is setup correctly have a look here:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://janikvonrotz.ch/images/keycloak-authentication-flow.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;As the final step navigate to &lt;em&gt;Configure &amp;gt; Clients &amp;gt; app.example.com &amp;gt; Settings &amp;gt; Authentication Flow Overrides&lt;/em&gt; and select for &lt;em&gt;Browser Flow&lt;/em&gt; the new authentication flow.&lt;/p&gt;&#xA;&lt;h1 id=&#34;test-the-login&#34;&gt;Test the login&lt;/h1&gt;&#xA;&lt;p&gt;Testing is quite simple. Create two users. One user has the client role &lt;em&gt;access&lt;/em&gt; assigned and the other user doesn&amp;rsquo;t. Open your client an initiate the auth process. Log in with the first user and the auth flow should work without interruption. Log in with the second user and you should see the error message &lt;em&gt;User is not allowed to access this client.&lt;/em&gt;.&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
