Software is hard
Software is hard

Writing a Keycloak-PKCE Library in C++

18 minutes read

In this article, we will be talking about a C++ library for using PKCE with Keycloak. Although I don’t consider myself a great C++ developer, I have worked with this programming language every now and then. And because I do a lot of work with Keycloak (check my other articles on that), I thought it would be a useful learning exercise to implement from scratch a library that supports PKCE (Proof-Key for Code Exchange) for Keycloak. Later, during the development of this library, I concluded that it would be a waste of a good API if I did not make it available via C. So, I wrote a small wrapper that offers a stable ABI to C99-compatible compilers. And because not everyone wants to deal with C++ or C directly, I wrote two additional wrappers for Python and Lua. Both of them use the C-API of this library, and I hope they’ll be helpful to others. I myself don’t intend to use this library for any production purposes, as I wrote it solely to learn more about PKCE and how everything works with Keycloak. But who knows, maybe it could be used to power secure communication. In fact, I strongly recommend using PKCE for every client that is incapable of securing its own credentials. These clients are mobile and web apps, many desktop applications, or basically anything that runs JavaScript. But before we dive in, here is the link to the repository. I am still extending the documentation and the test suite, so don’t expect everything to be there for direct consumption. However, the provided demos and example code are all working.

Writing a library in C++, really?

Yes, writing stuff in C++ is anything but pleasant, and I never quite liked the language. However, C++ is a sufficiently low-level language that still maintains a degree of useful structures like classes, interfaces, inheritance, polymorphism, and other good object-oriented features. Additionally, writing code in C++ requires a much higher “alertness” than, say, TypeScript or C#. Also, the outcomes of C++ can be later used to craft highly portable APIs, as I did with the C-API for this library. Even the most complex C++ code can be wrapped into an easy-to-reuse C-API. Try this with any other programming language. Modern variants of C++ also offer loads of new features that make dealing with structures, algorithms, strings, ranges, etc., much easier than it was in older versions of the language. All in all, it’s not that bad, and I consider myself rather a subpar programmer than C++ a bad programming language. Coding in C++ is like going to a gym. You might not like the toil, the salty sweat in your mouth, and the thick atmosphere, but you’ll surely love to see your body shape improving.

What is PKCE and why do we need it?

Proof-Key for Code Exchange is a security extension of the OAuth 2.0 protocol for secure client-side authentication. Those of you who develop client applications like web and mobile clients know of the inherent problem these solutions face: they very often cannot securely manage their credentials and other sensitive data. Storing passwords or tokens on the client side is a recipe for disaster sooner or later. To prevent interception attacks, protocols like PKCE have been invented. Instead of requiring the client to store a password or token locally, PKCE allows the client to create a verifier (a random string) and a code challenge (a hash of the verifier), which are used as follows:

  • Client creates code_verifier and code_challenge (the hash value of code_verifier)
  • Client starts authorization by sending the request containing the code_challenge
  • Authorization server (Keycloak) receives the request and stores the code_challenge
  • Authorization server returns an authorization code, which is bound to the code_challenge it received
  • Client sends authorization code and code_verifier
  • Authorization server compares the hash of the code_verifier with the previously stored code_challenge
  • Authorization server issues tokens only if the verification was successful
  • Client can now use tokens to access protected resources

The advantages are clear:

  • Interception of codes becomes ineffective
  • No shared secrets needed
  • Man-in-the-middle attacks are significantly mitigated
  • Replay attacks are too

If you are interested in learning how to use PKCE in a practical example with Angular and Keycloak, you can read this article.

C++ Library Structure

The C++ library is structured as follows:

  • The project uses CMake for building.
  • C++ Library sources are located in the subfolder “lib“.
  • The “examples” folder contains the C++ demo code that showcases the usage of the library.
  • Other languages and their examples are located in their respective subfolders inside “wrappers“.
  • Several external projects are used as git submodules. They are located in the “external” subfolder.
  • scripts” contains bash scripts I use to quickly create certificates, set up the C environment, run Lua scripts, and other goodies. Feel free to adapt them to your needs.
  • Under “keycloak” are docker-compose YAML files, a test realm JSON, and the nginx configuration files. This docker deployment uses Postgres as its database and (optionally) Nginx as a proxy server.
  • The “config” folder contains the default library_config JSON, the civetweb config (used by the C demo client), and the default app_config JSON used by the C++ demo client.
  • Inside the “docs” folder are markdown files that describe various parts of the library.
  • Tests are located in the “test” subfolder and currently only contain unit tests. I am still working on integration and e2e tests.
  • Under “certs” are self-signed certificates and CAs. Every language has its pair of keys/certs.

Core Interfaces

The most important interface is ITokenService that the KeycloakClient class implements.

It declares three methods:

The first and last of these are just helper methods that return information from the library configuration that gets read upon library instantiation. The real engine of the whole process is the exchange_code(…) method that uses the implementation of the IAuthenticationStrategy interface to communicate with Keycloak.

The methods in this interface are self-describing. The method create_authorization_url generates a new URL the client must visit to authenticate. Usually, a default Keycloak Login Form will be presented, or a template version of it. This is how it looks with the C demo client:

The C client generates a URL that, via a GET request, sends certain information to the Keycloak server. Among them are the expected response_type (code), the redirect_uri (which Keycloak will use on success to hand over tokens), the code_challenge from the client, and the scopes the client expects to receive. The first step is to enter your own credentials in Keycloak’s Login Form.

I have entered “test-user” and “password“, which you can use in the docker-compose deployment of Keycloak that comes with this library. At boot, the deployment imports a realm called “TestRealm” with this user and the OIDC Public Client named “test-client”. One must have an OpenID Connect Public Client setup to be able to use PKCE with the library. After the successful authentication, Keycloak will call the given redirect_uri to complete the PKCE auth flow. For this to succeed, the demo application must run an embedded server that handles certain routes. This, of course, has nothing to do with PKCE but rather with the usability of the various demo code provided. To avoid the need to run a separate web server, I used web servers like Crow, CivetWeb, lua-http, and OpenResty. As I am not an expert in any of these servers, I do not claim that the code I wrote is in any way highly performant or convention-compliant for the respective programming languages. But the demo code does work, as we can see in the final result of the C demo application, which is presented after the successful token exchange: a list of claims from the JWT token.

In the example above, I am using locally available DNS entries like “pkce-client.local.com” and “keycloak.local.com“. The provided self-signed certificates use the same DNS names too. Therefore, if your local setup differs or you are trying it in a different setting, ensure correct DNS names, routes, and certificates. Also, keep in mind that if you are using DevContainers as I do in VSCode, name resolution can become problematic because Docker uses its own DNS.

But now let’s continue talking about the other methods from the IAuthenticationStrategy interface, shall we? The handle_callback method is responsible for verifying the code received from Keycloak. For this check to succeed, PKCE’s StateStore must find the previously created state entry in its internal cache. If you are now asking yourself where this state object comes from, just go up and check the original authentication URL. Do you see the “state” parameter among others? The state (a random value) gets created and inserted into the StateStore (basically, a small cache) before the authentication starts. Keycloak receives it so that it can later send it back together with the code. Then, of course, the PKCE StateStore checks if the returned state object is still there (the cache deletes older entries automatically), and in a successful case, the code exchange will be allowed to continue. And just to be complete: state objects are used to prevent CSRF attacks. Ultimately, the KeycloakClient, which implements the ITokenService interface, will be allowed to execute the code exchange with Keycloak. These two interfaces, ITokenService (KeycloakClient) and IAuthenticationStrategy (PKCEStrategy), drive the PKCE auth flow.

Other Interfaces

There are of course other important implementations like the HttpClient, which relies on ASIO for SSL communication, but these are completely decoupled from the PKCE-relevant code and can be easily replaced. I have not invested that much time in implementing proper HTTP/SSL handling, as I was mostly focused on the PKCE side of things. I actually planned to write a strategy interface for HTTP communication so that both HTTP and HTTPS communication, with ASIO or any other networking library, would be possible, but I later decided to postpone it. I was mostly focused on PKCE. However, I will need to change HttpClient later, because without a proper strategy interface, everything boils down to ASIO and SSL, which makes changing code and especially testing it very hard (perhaps impossible?). HttpClient will be the first thing I throw out and reimplement.

Another useful piece of code is the Logger class that internally uses spdlog. However, I am still not completely satisfied, as I don’t want to dictate any kind of logging facility in the library. Maybe this should also be replaced with a stable interface or some kind of factory that creates logging facilities (if needed) for users who are actually interested in logging. But on the other hand, who wants to use std::cout or fmt::print for logging manually?

The other two very important but more or less easily replaceable mechanisms are the configuration classes. I separate them into two groups: library configuration and application configuration. The library configuration should be treated as something unchangeable and only then modified when internal changes happen. But in most cases, I think, you will only need to adapt your own application configuration. The application configuration can be of any format—or it doesn’t even need to exist at all (though I don’t recommend omitting it). The library configuration is always a JSON file named “library_config.json“. It contains the defaults that describe the Keycloak server, the PKCE behavior, and the cookie settings. All these things are “external” to the client in use and therefore should never be dictated by the client.

The application configuration either contains settings that govern the application behavior (like embedded web server settings) or, in some cases, the proxy settings that will be used to inform the PKCE library. These proxy settings will be used by the PKCE library to update its internals and establish communication channels with Keycloak. For example, when a web server that processes the callback routes Keycloak will use to exchange tokens is behind a network not directly accessible to Keycloak, a proxy server will allow forwarding such calls.

In any case, the application configuration must contain the redirect_uri setting, which the library uses to insert into the initial authentication URL leading to the Keycloak Login Form.

Because there are many different applications and use cases, there is actually no “correct” or “default” configuration possible. The only mandatory entry is the redirect_uri, but if your application only needs to set this value, then the usage of a configuration file itself might be questionable. Although the library configuration is by default in JSON format, there is no obligation to follow this rule. You can easily replace the internal mechanism of the ConfigLoader that instantiates the library configuration.

C API

After completing the basic design of the library, I asked myself if it should remain only with the C++ API (and ABI) for future clients. This would make the potential number of clients much lower and the implementation unnecessarily complex. Therefore, I decided to embark on another journey: providing a C API for the original C++ interfaces. It’s been a while (years?) since I wrote any meaningful amount of C code, so I am a bit out of touch with the latest developments in this area. I used C99 because it’s widely supported, and I also didn’t need anything more fancy than <stdbool.h>. I only tested the API under Ubuntu 22.04 and 24.04, so I cannot say how it will behave under other operating systems. I have a Windows box here but sadly no time to check everything. It’s still an experiment, I guess. The API is accompanied by a demo client that you can use to test and maybe also fix the API. The implementation of all wrapper code is located in the internal folder inside the API. The main implementation is in the kc_pkce.cpp file.

And after I wrote the C API, I thought it would be nice if we could test it in a language that is easier to use than C or C++.

Python Client

One of the two implemented users of the C API is the Python client, which uses the FFI declarations from keycloak_pkce.py. The client is fairly simple as it uses the default Python HTTP server, so there is no need to include another piece of software like I had to do with the C and C++ clients. We have everything inside the main() function, so running this demo is straightforward. The only thing you must configure is the path to the C API library. If you are using the CMake build system coming with the library, the binary will be located in build/c/lib/libkc_pkce.so. Set the environment variable KC_PKCE_LIB to point to this file. Then you can run the Python script as usual:

 

Lua Client

I have never written any serious code with Lua but always wanted to try something “realistic” with it. What better reason than writing an API wrapper, right? But what I didn’t know was that in the Lua world, there are some really amazing things to be found. Tools like OpenResty allow you to write Lua code directly in the nginx.conf and benefit from its performance. This, of course, brought the advantage of not having to search for a web server that could be embedded into the client code. Here, the web server is the client. In Lua, just like with Python, we rely on FFI (foreign-function-interface) that bridges between C and Lua/Python. Only the execution of this particular code is a bit different because we need to spin up the nginx web server to actually run Lua code. For this, we execute the openresty command from the build/lua folder:

openresty -p . -c nginx.conf -g "daemon off;"

This command will use the current path (-p) as the root and the configuration (-c) in the build/lua folder. The third option (-g) is optional, as it only prevents nginx from becoming a daemon so that you can stop it with CTRL+C. If you are frequently running and stopping openresty, I recommend using the nginx kill-script located here. Otherwise, you’ll sooner or later run into problems with blocked ports and orphaned nginx instances.

There is also a standalone.lua script available that behaves similarly to Python’s standalone.py script. However, unlike Python, it was difficult for me to find a good web server capable of using (self-signed?) certificates. I tried various solutions but failed too many times to run the server. Not sure why. Maybe because most of them were unmaintained for many years. I have no direct experience with the Lua world and don’t want to make any assumptions, but it’s clear that a “default solution,” like the one Python provides (a basic HTTP server), is simply not there. So, I ended up with a solution based on lua-http. More information on how to set up everything, including the needed packages, can be found here. To run the Lua standalone script comfortably, I recommend the run_lua_script.sh from the scripts folder. Otherwise, you’ll need to prepare the execution environment (luajit, etc.) manually.

Conclusion

I intended to write this library only for myself. It was an exercise, a test to see if I really understand PKCE well by using a programming language that is unforgiving and hard to grasp. I have already written something similar in TypeScript, which is a much easier language to use. But unlike TypeScript, I am not entirely satisfied with the C++ code that came out. I am not following good conventions and patterns as I should have. I have chained the library to ASIO by default. I have missed too many opportunities to craft useful factories and strategies. I have a full logging library getting compiled by default. And a bunch of other things too. But at least it works. And it has a C API I can play with, and two scripting languages use it already. Not bad, but also not quite good yet. But I hope it can serve as a show case of how the PKCE authorization flow works and which mechanisms are at play (both visible and invisible ones).

Have fun with Keycloak and PKCE!

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.