Friday, 30 October 2015

Getting started with Keycloak - Securing a REST Service

This is the second post in the Getting Started with Keycloak series. In this post we'll be securing a simple REST service with Keycloak. The example REST service is created using JAX-RS and deployed to WildFly.

Start a Keycloak Server

Follow the steps from the previous post in the series Installing the Keycloak Server as you will need to have a Keycloak server up and running. If you are planning to run the Keycloak server on the same machine make sure you start it on a different port:

bin/standalone.sh -Djboss.socket.binding.port-offset=100

Downloading the REST service example

You need to have Java 8 (or 7) and Maven 3 installed prior to building and deploying the REST service.

The first thing we need to do is to download the REST service. The service is hosted on GitHub so you should either fork and clone the project from GitHub or download an archive of the project.

To clone the project on GitHub simply run:

git clone https://github.com/stianst/keycloak-blog-gs.git

If you are not familiar with Git then simply download and extract the project from here.

A look at the Services

The services consists of only 3 Java classes inside services-jaxrs/src/main/java/org/keycloak/quickstart/jaxrs. The classes and what they do are:

  • Application - this is used to bootstrap the application. It contains an @ApplicationPath annotation which in this case instructs JAX-RS to deploy the services to '/' within the applications context-path
  • Resource - this is the actual service endpoints we're creating. It consists of 3 endpoints, public, secured and admin. They are very simply and only support GET requests. Each endpoint will simply return a message with the name of the endpoint invoked
  • Message - this represents the JSON structure returned by our endpoints. Again very simple and all the endpoints return are a message saying what endpoint was invoked

Deploying the services

To deploy the services first download and install WildFly 9.0.2.Final. You can download it from here. Once downloaded simply extract it to a directory and start it by running:

wildfly-9.0.2.Final/bin/standalone.sh
or if you are using Windows by running:
wildfly-9.0.2.Final/bin/standalone.bat

Once WildFly is up and running you can deploy the services by opening the directory you clone (or extracted) the project to. Then deploy them to WildFly by running:

cd services-jaxrs
mvn clean install wildfly:deploy

Once the services has been deployed you can open the following endpoints in your browser:

The services are not yet secured, but we will soon secure them using Keycloak.

Securing the services

Now let's secure the REST services. To do this open the web.xml file within the service sources (services-jaxrs/src/main/webapp/WEB-INF/web.xml). Then add the following snippet:

<security-constraint>
    <web-resource-collection>
        <url-pattern>/secured</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>user</role-name>
    </auth-constraint>
</security-constraint>

<security-constraint>
    <web-resource-collection>
        <url-pattern>/admin</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>admin</role-name>
    </auth-constraint>
</security-constraint>

<login-config>
    <auth-method>BASIC</auth-method>
</login-config>

<security-role>
    <role-name>admin</role-name>
    <role-name>user</role-name>
</security-role>
This configures the container (in this case WildFly, well actually Undertow is the web server on WildFly) to require the users to have certain roles to be allowed to invoke the endpoints. In this case the endpoint secured requires the role user and the endpoint admin requires the role admin.

Now re-deploy the services by running:

cd services-jaxrs
mvn install wildfly:deploy

Now you can again try to invoke the endpoints:

You should only be able to invoke the public endpoint. The two other endpoints require a user to be authenticated and the user should have the correct roles to access the service. However, we've not specified how users should be authenticate, nor do we have any users, so you won't be able to login to invoke the secured or admin endpoints.

Creating a Client in Keycloak

Now we need to create a client for the services within Keycloak. To do this open Keycloak admin console in your browser. Sign in and click on Clients in the menu on the left hand side. Once that's open click on Create on top of the table. On the next screen fill in the following values:

  • Client ID: service
  • Access Type: bearer-only
Then click on Save. In summary what you've just done is to configure a client with Keycloak. The client id is used for the client to identify itself to Keycloak. Setting the access type to bearer-only means that client will only verify bearer tokens and can't obtain the tokens itself. This means the service is not going to attempt to redirect users to a login page on Keycloak. This is perfect as we're deploying a service intended to be invoked by an application and not by users directly.

Now click on the Installation tab on the top of the form. Under format option select Keycloak JSON. Click on the Download tab. You should move the downloaded file (keycloak.json to services/src/main/webapp/WEB-INF. This provides the required configuration for the Keycloak client adapter.

There's also an alternative method when deploying to WildFly (or JBoss EAP) which is to specify the configuration found in keycloak.json inside standalone.xml instead. We're not going to cover this approach in this post, but it can be more convinient to use this option if you don't want to open up your WAR (or change the source like we did now).

You should also open services/src/main/webapps/WEB-INF/web.xml in your favorite editor and change:

BASIC
To:
KEYCLOAK

Don't try to redeploy the WAR just yet as we have to first install the Keycloak client adapter into WildFly.

Create User and Roles

Now we need to create a user that has the correct roles to access the endpoints. Again open the Keycloak admin console in your browser. First click on Roles there's already an admin role so we don't need to add that, but we need to create the user role so click on Add Role. Use user for the Role name and click Save.

We've now have the two roles we need for our service, next step is to create a user. Click on Users in the menu. Then click on Add User. Set the username to user and click on Save. Then click on Credentials. Enter a password in the password and password confirmation fields. Click on the toggle next to Temporary so it displays OFF. This prevents the user from having to reset the password on the next login. Now click on Role Mappings. To demonstrate that this user is only allowed to invoke the secured endpoint, not the admin endpoint, we're going to only assign the user role to this user. Under Realm Roles in Available Roles select user and click Add selected. Now we're done configuring Keycloak.

Install Client Adapter into WildFly

Next thing we're going to do is to install the Keycloak client adapter into WildFly. Download the WildFly client adapter keycloak-wf9-adapter.tar.gz (or keycloak-wf9-adapter.zip). Extract the archive into the root of your WildFly installation and run:

bin/jboss-cli.sh -c ':shutdown(restart=true)'
Wait until WildFly has restarted then run:
bin/jboss-cli.sh -c --file=bin/adapter-install.cli
Finally run:
bin/jboss-cli.sh -c ':shutdown(restart=true)'

Re-deploy services

Now we've installed the WildFly client adapter we can re-deploy the service with the Keycloak config. To do this run:

cd services-jaxrs
mvn install wildfly:deploy

Once the services has been re-deployed now try the endpoints again:

Now if you try to access the secured or admin endpoint you will no longer get the basic prompt for username and password, but simply a message saying Unauthorized. That's because you haven't included an access token in your request. Next steps we will do is to manually obtain an access token and invoke the services using CURL so that we can pass the token along with the request.

Obtain Token and Invoke Service

First we need to create a client that can be used to obtain the token. Go to the Keycloak admin console again and create a new client. This time give it the Client ID curl and select public for access type. Under Valid Redirect URIs enter http://localhost.

As we are going to manually obtain a token and invoke the service let's increase the lifespan of tokens slightly. In production access tokens should have a relatively low timeout, ideally less than 5 minutes. To increase the timeout go to the Keycloak admin console again. This time click on Realm Settings then on Tokens. Change the value of Access Token Lifespan to 15 minutes. That should give us plenty of time to obtain a token and invoke the service before it expires.

Now we're ready to get our first token using CURL. To do this run:

RESULT=`curl --data "grant_type=password&client_id=curl&username=user&password=password" http://localhost:8180/auth/realms/master/protocol/openid-connect/token`
This is a bit cryptic and luckily this is not how you should really be obtaining tokens. Tokens should be obtained by web applications by redirecting to the Keycloak login page. We're only doing this so we can test the service as we don't have an application that can invoke the service yet. Basically what we are doing here is invoking Keycloaks OpenID Connect token endpoint with grant type set to password which is the Resource Owner Credentials flow that allows swapping a username and a password for a token.

Take a look at the result by running:

echo $RESULT
The result is a JSON document that contains a number of properties. There's only one we need for now though so we need to parse this output to retrieve only the value we want. To do this run:
TOKEN=`echo $RESULT | sed 's/.*access_token":"//g' | sed 's/".*//g'`
This command uses sed to strip out everything before and after the value of the access token property. I'm afraid these instructions will only work on Linux or iOS so Windows users will have to figure out how to do this themselves or install Cygwin. If anyone knows how to do this on Windows I'd appreciate if you'd add the instructions as a comment to this post.

Now that we have the token we can invoke the secured service. To do this run:

curl http://localhost:8080/service/secured -H "Authorization: bearer $TOKEN"

26 comments:

  1. thanks for these tutorials, they are really helpful

    I would love to see more.

    ReplyDelete
  2. Thanks for the tutorial!

    I have a question, though. I am using the same approach as described in the tutorial (Access Type: bearer-only). I would like to retrieve the name of the current user programmatically on the back-end. As far as I understand, information about the user is stored in id_token. Is there any way to pass this id_token with a GET request? Since keycloakSecurityContext.getIdToken(); returns null for me.

    ReplyDelete
    Replies
    1. You should get the details from the Access Token (KeycloakSecurityContext#getAccessToken), not the ID token. The ID Token is not sent to the back-end (bearer-only) as it's used for authentication, not for access. It's only available to the front-end.

      Delete
  3. Hi, I'm following the blog, I got stagnate in `bin/jboss-cli.sh -c ':shutdown(restart=true)'` step. I use windows 7 and this is the problem with the adpater:

    Unable to read the logging configuration from 'file:/d/Projects/wildfly-9.0.2.Final/bin/jboss-cli-logging.properties' (java.io.FileNotFoundException: \d\Projects\wildfly-9.0.2.Final\bin\jboss-cli-logging.properties (The system can not find the path specified))

    Thanks in advance if somebody can help me.

    ReplyDelete
    Replies
    1. Does the file 'jboss-cli-logging.properties' exist? If you are having issues with jboss-cli you can also add it manually. Take a look at http://keycloak.github.io/docs/userguide/keycloak-server/html/ch08.html#jboss-adapter. You only need to add 3 elements to standalone.xml.

      Delete
    2. Stian, my error was trivial as I'm working in W7, I have to run `bin/jboss-cli.bat -c ':shutdown(restart=true)'` (.bat, not .sh). Thanks.

      Delete
  4. Thanks Stian, yes I'm working on it. It would be nice to add more information in step "Creating a Client in Keycloak" because it is not all clear how to run the admin console, I would add a "see keycloak install in wildfly 9" link. I hope to help.

    ReplyDelete
    Replies
    1. Right at the top in this post there's a section "Start a Keycloak Server". This links to the previous blog post in this series that gives an introduction on how to install the Keycloak Server. If you don't know how to open the Keycloak admin console please go through that guide.

      Delete
  5. Why do you create another client (curl)? Why not use the client called service?

    ReplyDelete
    Replies
    1. Service is a 'bearer-only' client and is not permitted to obtain tokens.

      Delete
    2. Where is the link between curl and service clients? we are getting token for curl client and we service is authenticating against service !

      Delete
    3. The curl client is allowed to obtain tokens that gives it permission to invoke the service on behalf of a user. That requires the curl client to have a scope on the roles of the service and also that the user has the required roles.

      Delete
    4. This comment has been removed by the author.

      Delete
    5. Please use our user mailing list for general questions. See keycloak.org/community.html for more details.

      Delete
  6. Thanks for the tutorial! But both of links with example application are corrupted:

    https://github.com/stianst/keycloak-blog-gs.git
    https://github.com/stianst/keycloak-blog-gs/archive/master.zip

    ReplyDelete
    Replies
    1. I'm afraid I removed the example app while cleaning up old stuff from my Github account.

      Delete
    2. An updated version of the examples are available at:
      https://github.com/redhat-developer/redhat-sso-quickstarts/tree/7.0.x/service-jee-jaxrs

      Delete
  7. This comment has been removed by the author.

    ReplyDelete
  8. Hi.

    Is there some keycloak API to call /auth/realms/master/protocol/openid-connect/token?
    Thank you

    ReplyDelete
    Replies
    1. It's standard OpenID Connect / OAuth 2. Please look at our documentation and ask general questions on our user mailing list.

      Delete
  9. Hi, thanks for the nice artilce. I think this does not work with KeyCloak 2.5.4. I am not able to define Redirect URI with public clients. Is there any solution to do the same with new KeyCloak?
    Thanks.

    ReplyDelete
    Replies
    1. Same works in 2.5.4, but there are some changes to the screens. Just create the client first, then you'll see a new screen with more fields where you can define redirect URIs for public clients in Keycloak 2.5.4 as well. If you have more issues please check docs and ask on the user mailing list.

      Delete
  10. Hi, i triedout the Qickstart with JaxRs from Keycloak but after deploying i get an error message. Starting of Keycloak + Wildfly works great. Now I`m confused :-( Please help.

    Can anyone Help Me and give me a tip to solving it?


    c:\Quickstarts\service-jee-jaxrs>mvn install wildfly:deploy
    [INFO] Scanning for projects...
    [ERROR] [ERROR] Some problems were encountered while processing the POMs:
    [ERROR] Non-resolvable import POM: Could not find artifact org.keycloak.bom:keycloak-adapter-bom:pom:3.0.0.CR1-SNAPSHOT @ line 45, column 25
    @
    [ERROR] The build could not read 1 project -> [Help 1]

    ReplyDelete
  11. I want to make my jax-rs service secure using keycloak authentication. My login service is already secured and returning bearer token as well.

    But in this case I want to access another service using bearer token returned by login service which is running on another port.How can I achieve that ?

    E.g My client registered with url like "http://localhost:8080/authentication-service" on keycloak and my new service is running on another port like http://localhost:8081/my-service.

    Please guide me on this ...

    ReplyDelete
  12. how to invalidate accesstoken using keycloak api's ?

    ReplyDelete

Please only add comments directly associated with the post. For general questions use the Keycloak user mailing list.