Trying to Authenticate in a Demo Application using OpenID Connect (OIDC) using Keycloak

I was trying out Keycloak as part of evaluating OpenID Connect providers for Vault and any other applications we will host at my company

In this post, let's look at how I tried out Keycloak OpenID Connect login in my local along with an example demo app from dex repository - https://github.com/dexidp/dex/tree/daf1bf256486ada5d7a70f3dbf6bcd7f55622284/examples/example-app

First, let's run Keycloak using Docker using a Keycloak container image, all in local

Before running Keycloak using Docker, let me show you the version information ℹ️

$ docker run --rm keycloak/keycloak --version
Keycloak 23.0.5
JVM: 17.0.10 (Red Hat, Inc. OpenJDK 64-Bit Server VM 17.0.10+7-LTS)
OS: Linux 6.5.11-linuxkit amd64

Here, we are using the Keycloak container image from Docker Hub - https://hub.docker.com/r/keycloak/keycloak and the version I'm using is the latest version as of this writing ✍️ , which is 23.0.5 - https://github.com/keycloak/keycloak/releases/tag/23.0.5

I use no image tags while running the container image, so the default image tag of latest is taken up and latest container image is picked up. You can also run using the 23.0.5 image tag, like this -

$ docker run --rm keycloak/keycloak:23.0.5 --version

It will look something like this -

$ docker run --rm keycloak/keycloak:23.0.5 --version
Unable to find image 'keycloak/keycloak:23.0.5' locally
23.0.5: Pulling from keycloak/keycloak
Digest: sha256:8772ab5e763f3374b7bb70bce4a96ad384b39c548c40007113538e103ee8a8f2
Status: Downloaded newer image for keycloak/keycloak:23.0.5
Keycloak 23.0.5
JVM: 17.0.10 (Red Hat, Inc. OpenJDK 64-Bit Server VM 17.0.10+7-LTS)
OS: Linux 6.5.11-linuxkit amd64

Also, you can run with the help flag to see all the flags and options you have -

$ docker run --rm keycloak/keycloak:23.0.5 --help

It will look something like this -

$ docker run --rm keycloak/keycloak:23.0.5 --help
Keycloak - Open Source Identity and Access Management

Find more information at: https://www.keycloak.org/docs/latest

Usage:

kc.sh [OPTIONS] [COMMAND]

Use this command-line tool to manage your Keycloak cluster.

Options:

-cf, --config-file <file>
                     Set the path to a configuration file. By default, configuration properties are
                       read from the "keycloak.conf" file in the "conf" directory.
-h, --help           This help message.
-v, --verbose        Print out error details when running this command.
-V, --version        Show version information

Commands:

  build                   Creates a new and optimized server image.
  start                   Start the server.
  start-dev               Start the server in development mode.
  export                  Export data from realms to a file or directory.
  import                  Import data from a directory or a file.
  show-config             Print out the current configuration.
  tools                   Utilities for use and interaction with the server.
    completion            Generate bash/zsh completion script for kc.sh.

Examples:

  Start the server in development mode for local development or testing:

      $ kc.sh start-dev

  Building an optimized server runtime:

      $ kc.sh build <OPTIONS>

  Start the server in production mode:

      $ kc.sh start <OPTIONS>

  Enable auto-completion to bash/zsh:

      $ source <(kc.sh tools completion)

  Please, take a look at the documentation for more details before deploying in
production.

Use "kc.sh start --help" for the available options when starting the server.
Use "kc.sh <command> --help" for more information about other commands.

Now let's run (start) the Keycloak server to see it run and use it

$ docker run --rm -p 8080:8080 -e KEYCLOAK_ADMIN="admin" -e KEYCLOAK_ADMIN_PASSWORD="admin" keycloak/keycloak start-dev

I'm running Keycloak in development mode. Please DO NOT use this configuration in production

When you run the above command, it will look something like this -

$ docker run --rm -p 8080:8080 -e KEYCLOAK_ADMIN="admin" -e KEYCLOAK_ADMIN_PASSWORD="admin" keycloak/keycloak start-dev
Updating the configuration and installing your custom providers, if any. Please wait.
2024-02-01 14:20:42,750 INFO  [io.quarkus.deployment.QuarkusAugmentor] (main) Quarkus augmentation completed in 6640ms
2024-02-01 14:20:44,238 INFO  [org.keycloak.quarkus.runtime.hostname.DefaultHostnameProvider] (main) Hostname settings: Base URL: <unset>, Hostname: <request>, Strict HTTPS: false, Path: <request>, Strict BackChannel: false, Admin URL: <unset>, Admin: <request>, Port: -1, Proxied: false
2024-02-01 14:20:45,906 WARN  [io.quarkus.agroal.runtime.DataSources] (main) Datasource <default> enables XA but transaction recovery is not enabled. Please enable transaction recovery by setting quarkus.transaction-manager.enable-recovery=true, otherwise data may be lost if the application is terminated abruptly
2024-02-01 14:20:46,444 WARN  [org.infinispan.PERSISTENCE] (keycloak-cache-init) ISPN000554: jboss-marshalling is deprecated and planned for removal
2024-02-01 14:20:46,582 WARN  [org.infinispan.CONFIG] (keycloak-cache-init) ISPN000569: Unable to persist Infinispan internal caches as no global state enabled
2024-02-01 14:20:46,683 INFO  [org.infinispan.CONTAINER] (keycloak-cache-init) ISPN000556: Starting user marshaller 'org.infinispan.jboss.marshalling.core.JBossUserMarshaller'
2024-02-01 14:20:47,300 INFO  [org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory] (main) Node name: node_354000, Site name: null
2024-02-01 14:20:50,550 INFO  [org.keycloak.quarkus.runtime.storage.legacy.liquibase.QuarkusJpaUpdaterProvider] (main) Initializing database schema. Using changelog META-INF/jpa-changelog-master.xml

UPDATE SUMMARY
Run:                        117
Previously run:               0
Filtered out:                 0
-------------------------------
Total change sets:          117

2024-02-01 14:20:55,112 INFO  [org.keycloak.broker.provider.AbstractIdentityProviderMapper] (main) Registering class org.keycloak.broker.provider.mappersync.ConfigSyncEventListener
2024-02-01 14:20:55,153 INFO  [org.keycloak.services] (main) KC-SERVICES0050: Initializing master realm
2024-02-01 14:20:56,830 INFO  [io.quarkus] (main) Keycloak 23.0.5 on JVM (powered by Quarkus 3.2.10.Final) started in 13.965s. Listening on: http://0.0.0.0:8080
2024-02-01 14:20:56,830 INFO  [io.quarkus] (main) Profile dev activated. 
2024-02-01 14:20:56,830 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, jdbc-h2, jdbc-mariadb, jdbc-mssql, jdbc-mysql, jdbc-oracle, jdbc-postgresql, keycloak, logging-gelf, micrometer, narayana-jta, reactive-routes, resteasy-reactive, resteasy-reactive-jackson, smallrye-context-propagation, smallrye-health, vertx]
2024-02-01 14:20:57,212 INFO  [org.keycloak.services] (main) KC-SERVICES0009: Added user 'admin' to realm 'master'
2024-02-01 14:20:57,216 WARN  [org.keycloak.quarkus.runtime.KeycloakMain] (main) Running the server in development mode. DO NOT use this configuration in production.

Now you can go to http://localhost:8080/ and see the Keycloak page -

Next, you can click on Administration Console and it will go to http://localhost:8080/admin/ and it will look like this -

http://localhost:8080/admin/ will redirect to http://localhost:8080/admin/master/console/ and then it will get redirected to a big URL like the one shown in the image, something like this - http://localhost:8080/realms/master/protocol/openid-connect/auth?client_id=security-admin-console&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fadmin%2Fmaster%2Fconsole%2F&state=7ead29bc-461d-4745-84f0-ad062b76d9b7&response_mode=fragment&response_type=code&scope=openid&nonce=4addb02c-2c79-47c2-881a-2043edbb6c63&code_challenge=-puynJjEbdexstTP-hw908cORoWAmI70QHQwoxSpQtQ&code_challenge_method=S256

You can use the credentials - Username: admin and Password: admin

And then, after login, it will look something like this -

Next, create a client in Keycloak for the demo application. A client that we will use to setup the demo application's authentication using Keycloak OpenID Connect

On the left, choose Clients section, and it will go to a page that looks like this -

Click Create Client button and it will go to a new page that looks like this -

Let's just name the client as Example and give it an ID example and give a sample description like Example App, just for demo purposes. It will look like this -

Next, click on Next and enable Client authentication, we won't do any Authorization stuff for now, so, leave it. We will just enable Standard flow for now in Authentication flow . It will look something like this -

Next, click on Next and put http://127.0.0.1:5555/callback for Valid redirect URIs for now and then click Save . It will look something like this -

After saving, it will look something like this -

Now, go to Credentials section and get the Client secret key value. It will look something like this -

In my case, the client secret is ELwPXpMDSq2AnlKRQx8dUZhRemVEsspp

Now, let's open up our demo application

https://github.com/dexidp/dex/tree/daf1bf256486ada5d7a70f3dbf6bcd7f55622284/examples/example-app

I cloned the dex repository and built the example-app using make examples . Note that this requires Golang to be installed. It will look like this -

$ make examples
github.com/dexidp/dex/examples/example-app

Finally, you can run the example-app like this -

$ ./bin/example-app --help
An example OpenID Connect client

Usage:
  example-app [flags]

Flags:
      --client-id string        OAuth2 client ID of this application. (default "example-app")
      --client-secret string    OAuth2 client secret of this application. (default "ZXhhbXBsZS1hcHAtc2VjcmV0")
      --debug                   Print all request and responses from the OpenID Connect issuer.
  -h, --help                    help for example-app
      --issuer string           URL of the OpenID Connect issuer. (default "http://127.0.0.1:5556/dex")
      --issuer-root-ca string   Root certificate authorities for the issuer. Defaults to host certs.
      --listen string           HTTP(S) address to listen at. (default "http://127.0.0.1:5555")
      --redirect-uri string     Callback URL for OAuth2 responses. (default "http://127.0.0.1:5555/callback")
      --tls-cert string         X509 cert file to present when serving HTTPS.
      --tls-key string          Private key for the HTTPS cert.

That shows the help options when using --help flag. Now, let's run it! :)

We know the client ID and client secret and the issuer too, which I'll share, let's put together all these :)

$ ./bin/example-app --issuer http://localhost:8080/realms/master --client-id example --client-secret 'ELwPXpMDSq2AnlKRQx8dUZhRemVEsspp'

It will look something like this -

$ ./bin/example-app --issuer http://localhost:8080/realms/master --client-id example --client-secret 'ELwPXpMDSq2AnlKRQx8dUZhRemVEsspp' 
2024/02/01 20:21:17 listening on http://127.0.0.1:5555

Now, go to http://localhost:5555/ in an incognito window. It will look something like this -

Next, just click Login , and it will go to a page that looks like this -

With the URL looking something like this -

http://localhost:8080/realms/master/protocol/openid-connect/auth?client_id=example&redirect_uri=http%3A%2F%2F127.0.0.1%3A5555%2Fcallback&response_type=code&scope=openid+profile+email+offline_access&state=I+wish+to+wash+my+irish+wristwatch

Now, we know only one user that's present in Keycloak, which is the admin user, so, let's use the admin user credentials and login to it - Username: admin and Password: admin

It will look something like this -

Finally, just click Login . Finally, you will see the token you got from the Keycloak OpenID Connect Provider. It will look something like this -

With the URL looking something like this -

http://127.0.0.1:5555/callback?state=I+wish+to+wash+my+irish+wristwatch&session_state=a9af4b83-d377-4ab0-b602-d1489c7eb40f&iss=http%3A%2F%2Flocalhost%3A8080%2Frealms%2Fmaster&code=8665a25f-448e-4b41-94f3-f86050098a55.a9af4b83-d377-4ab0-b602-d1489c7eb40f.aaacaa02-95a8-4e6b-a814-844040520a13

You can also click Redeem refresh token and it will show you a new 🆕 token for ID token, Access token and Refresh token too :)

If you refresh this page, it will show an error, which makes sense. It will look like this -

As it correctly says, the code is not valid anymore

You can refresh the page once the Redeem refresh token is clicked though. I'll probably write another post talking about the internals of this whole flow and why it works in some refresh and why and how it doesn't work in other refresh :)

So, that's how you do a basic authentication using OpenID Connect using Keycloak :) 🔑 🔐

That's all for this blog post. I'll see you in the next one :D :)