Showing posts for tag "keycloak"

Integrating External Java Apps With Keep And Keycloak

Wed May 03 09:43:59 EDT 2023

Last year, I wrote a post describing some early work on a Jakarta NoSQL driver for the Domino REST API (hereafter referred to as "Keep" to avoid ambiguity with the various other Domino REST APIs).

I've since picked back up on the project and similar aspects, and I figured it'd be useful to return to provide some more details.

OpenAPI

For starters, I mentioned in passing my configuration of the delightful openapi-generator tool, but didn't actually detail my configuration. It's changed a little since my first work, since I found where you can specify using the jakarta.* namespace.

I use a config.yaml file like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
additionalProperties:
  library: microprofile
  dateLibrary: java8
  apiPackage: org.openntf.xsp.nosql.communication.driver.keep.client.api
  invokerPackage: org.openntf.xsp.nosql.communication.driver.keep.client
  modelPackage: org.openntf.xsp.nosql.communication.driver.keep.client.model
  useBeanValidation: true
  useRuntimeException: true
  openApiNullable: false
  microprofileRestClientVersion: "3.0"
  useJakartaEe: true

That will generate client interfaces that will mostly compile in a plain Jakarta EE project. The files have some references to an implementation-specific MIME class to work around JAX-RS's historical lack of one, but those imports can be safely deleted.

Keycloak/OIDC in Keep

I also mentioned only in passing that you could configure Keep to trust the Keycloak server's public keys with a link to the documentation. Things on the Keep side have expanded since then, and you can now configure Keep to reference Keycloak using Vert.x's internal OIDC support, and also skip the step of creating special fields in your person docs to house the Notes-format DN. For example, in a Keep JSON config file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
	"oidc": {
		"my-keycloak": {
			"active": true,
			"providerUrl": "https://my.keycloak.server/auth/realms/myrealm",
			"clientId": "keep-app",
			"clientSecret": "<my secret>",
			"userIdentifierInLdapFormat": true
		}
	}
}

That will cause Keep to fetch much of the configuration information from the well-known endpoint Keycloak exposes, and also to map names from Keycloak from the LDAP-style format of "cn=Foo Fooson,o=SomeOrg" to Domino-style "CN=Foo Fooson/O=SomeOrg". This is useful even when using Domino as the Keycloak LDAP backend, since Domino does the translation in the other direction first.

Keycloak/OIDC in Jakarta EE

In the original post in the series, talking about configuring app authentication for the AppDev Pack, I talked about Open Liberty's openidConnectClient feature, which lets you configure OIDC at the server level. That's neat, and I remain partial to putting authentication at the server level when it makes sense, but it's no longer the only game in town. The version of Jakarta Security that comes with Jakarta EE 10 supports OIDC inside the app in a neat way, and so I've switched to using that.

To do that, you make a CDI bean that defines your OIDC configuration - this can actually be on a class that does other things as well, but I like putting it in its own place:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package config;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.security.enterprise.authentication.mechanism.http.OpenIdAuthenticationMechanismDefinition;
import jakarta.security.enterprise.authentication.mechanism.http.openid.ClaimsDefinition;

@ApplicationScoped
@OpenIdAuthenticationMechanismDefinition(
	clientId="${oidc.clientId}",
	clientSecret="${oidc.clientSecret}",
	redirectURI="${baseURL}/app/",
	providerURI="${oidc.domain}",
	claimsDefinition = @ClaimsDefinition(
		callerGroupsClaim = "groups"
	)
)
public class AppSecurity {
}

There are a couple EL references here. baseURL is provided for "free" by the framework, allowing you to say "wherever the app is hosted" without having to hard-code it. oidc here refers to a bean I made that's annotated with @Named("oidc") and has getters like getClientId() and so forth. You can make a class like that to pull in your OIDC config and secrets from outside, such as a resource file, environment variables, or so forth. providerURI should be the same base URL as Keep uses above.

Once you do that, you can start putting @RolesAllowed annotations on resources you want protected. So far, I've been using @RolesAllowed("users"), since my Keycloak puts all authenticated users in that group, but you could mix it up with "admin" or other meaningful roles per endpoint. For example, inside a JAX-RS class:

1
2
3
4
5
6
7
@Path("superSecure")
@GET
@Produces(MediaType.TEXT_PLAIN)
@RolesAllowed("users")
public String getSuperSecure() {
	return "You're allowed in!";
}

When accessing that endpoint, the app will redirect the user to Keycloak (or your OIDC provider) automatically if they're not already logged in.

Accessing the Token

In my previous posts, I mentioned that I was able to access the OIDC token that the server used by setting accessTokenInLtpaCookie in the Liberty config, and then getting oidc_access_token from the Servlet request object's attributes, and that that only showed up on requests after the first.

The good news is that, with the latest Jakarta Security, there's a standardized way to do this. In a CDI bean, you can inject an OpenIdContext object to get the current user's token:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package bean;

import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.security.enterprise.identitystore.openid.OpenIdContext;

@RequestScoped
public class OidcContextBean {
  
	@Inject
	private OpenIdContext context;
  
	public String getToken() {
		// Note: if you don't restrict everything in your app, do a null check here
		return context.getAccessToken().getToken();
	}
}

There are other methods on that OpenIdContext object, providing access to specific claims and information from the token, which would be useful in other situations. Here, I only really care about the token as a string, since that's what I'll send to Keep.

With that token in hand, you can build a MicroProfile Rest Client using the generated API interfaces. For example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class SomeClass {
	/* snip */
	@Inject
	private OidcContextBean oidcContext;

	/* snip */

	private DataApi getDataApi() {
		return RestClientBuilder.newBuilder()
			.baseUri("http://your.keep.server:8880/api/v1/")
			.register((ClientRequestFilter) (ctx) -> {
				ctx.getHeaders().add(HttpHeaders.AUTHORIZATION, "Bearer " + oidcContext.getToken()); //$NON-NLS-1$
			})
			.build(DataApi.class);
	}
}

That will cascade the OIDC token used for your app login over to Keep, allowing your app to access data on behalf of the logged-in user smoothly.

I've been kicking the tires on some example apps and fleshing out the Jakarta NoSQL driver using this, and it's been going really smoothly so far. Eventually, my goal will be to make it so that you can take code using the JNoSQL driver for Domino inside an NSF using the XPages JEE project and move it with minimal changes over to a "normal" JEE app using Keep for access. There'll be a bit of rockiness in that the upstream JNoSQL API is changing a bit to adapt to Jakarta Data and will do so in time for JEE to require Java 21, but at least it won't be too painful an analogy.

Tinkering with Mastodon, Keycloak, and Domino

Thu Nov 10 13:01:00 EST 2022

Tags: admin keycloak

Because of what I'll euphemistically call the current historical moment on Twitter, I (like a lot of people) decided to give another look at Mastodon. The normal way one would use it would be to sign up at mastodon.social and be on one's merry way, treating it just like a slightly-different Twitter.

However, Mastodon is intentionally designed to be federated in a way similar to email, and the software is available on GitHub complete with scripts for Docker Compose, Vagrant, and so forth. So I went and did that, setting up my currently-barely-used account at @jesse@pub.frostillic.us.

That on its own isn't particularly notable, nor are the specifics of how I set it up (it was a hodgepodge of a couple posts you can find by looking for "mastodon docker compose"). What I found neat for our purposes here was the way I could piggyback authentication onto stuff I had recently done with Keycloak. Keycloak, incidentally, was the topic of today's OpenNTF webinar, so, if you didn't see it, check back there for the replay when it's posted.

Having done the legwork for setting up Keycloak backed by Domino LDAP for my earlier tinkering, the setup to work with Mastodon was pretty straightforward (as far as these things go). I did the professional thing and took the basic config from a StackOverflow post, tweaking it to suit my needs.

The main Domino-y thing I wanted to tweak here was the username that I ended up with on Mastodon. Internally, the Domino short name for my account is "jgallagh", but I like to go by "jesse" when in an environment small enough to get away with it. So I cracked open the names.nsf subform I had added years ago for POSIX and SSH pubkey purposes and added a Mastodon section:

Screenshot of a 'Mastodon Attributes' section in a names.nsf

(apologies for how bad the new-era fonts look in my poor old Windows VM)

Then, I told my Mastodon config about that field for the UID:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
OIDC_ENABLED=true
OIDC_DISPLAY_NAME=frostillic.us Auth
OIDC_DISCOVERY=true
OIDC_ISSUER=https://<keycloak_url>/auth/realms/<real>
OIDC_AUTH_ENDPOINT=https://<keycloak_url>/auth/realms/<real>/.well-known/openid-configuration
OIDC_SCOPE=openid,profile,email
OIDC_UID_FIELD=mastodonusername
OIDC_CLIENT_ID=<client id>
OIDC_CLIENT_SECRET=<client secret>
OIDC_REDIRECT_URI=https://<mastodon URL>/auth/auth/openid_connect/callback
OIDC_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true

On Keycloak, I made a new realm to cover this sort of "personal" setup to be able to split the user pool and then added a Client definition for Mastodon. I set it up as "Access Type" "confidential" and grabbed the client ID and secret for the config above and then configured the Redirect URI. To get the custom username field over from LDAP, I added a "user-attribute-ldap-mapper" Mapper in the LDAP User Federation definition to bring it in. Then, back in the Client definition, I added a "User attribute" token mapper to the config to bring this in as well so it's added to the JWT.

That covered the auth config, and it's been working well since. When you have OIDC configured in your Mastodon config, it sprouts a button below the main login mechanically labeled "OPENID_CONNECT":

Screenshot of a Mastodon login form with OIDC configured

Clicking that will send you to the configured Keycloak page to do the OIDC dance and, when all goes well, back to a freshly-configured Mastodon account.

Now, technically, this doesn't really gain me much that I couldn't have gotten by configuring the users separately in the Mastodon instance, but the experience is useful. I'm gradually getting really sold on the idea of having a multi-purpose Keycloak instance to handle authentication and authorization. Most of the time, it's a thin layer over what you could get by pointing to Domino LDAP from these disparate apps themselves. However, there are some benefits in that Keycloak is now the only one that has to deal with Domino's weird LDAP and also this gives me a lot of room for fine-grained control and federation with other providers. It's just neat.