OIDC4J
OIDC4J - A Java library for building OAuth2 and OIDC servers
OIDC4J is a library built in Java to provide Oauth2 and OIDC server functionality for use in your own projects.
It is not in itself a server application, but a library which facilitates the creation of one.
Getting OIDC4J
OIDC4J is available on Maven Central. There are two libraries, lib-oidc4j and commons. The former contains the core functionality, and the latter contains some utility classes which can be useful.
To use it in your Maven project, add the following to your pom.xml:
<dependency>
<groupId>com.elevenware.oidc4j</groupId>
<artifactId>lib-oidc4j</artifactId>
<version>0.0.2</version>
</dependency>
<dependency>
<groupId>com.elevenware.oidc4j</groupId>
<artifactId>commons</artifactId>
<version>0.0.2</version>
</dependency>
To use it in a gradle project, add the following to your build.gradle:
implementation 'com.elevenware.oidc4j:lib-oidc4j:0.0.2'
implementation 'com.elevenware.oidc4j:commons:0.0.2'
You may also use the latest snapshot versions of these libraries, by adding the following to your repositories section:
<repository>
<id>ossrh</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</repository>
or in gradle:
repositories {
...
maven {
url = "https://oss.sonatype.org/content/repositories/snapshots/"
}
}
Building from source
This is a gradle project, which comes with the gradle wrapper. You may build this and use it locally by running:
./gradlew clean build publishToMavenLocal
Using OIDC4J
To use OIDC4J, you will need to create a web application which translates HTTP requests into objects which OIDC4J can understand, and vice versa. Let’s implement a very simple example using Javalin. You can also view this example in the examples/javalin-cc folder of this repository.
public class Application {
public static void main(String[] args) {
String baseUrl = "http://localhost:8091";
// A barebones configuration
ProviderConfig config = ProviderConfig.builder()
.baseUrl(baseUrl)
.grantTypesSupported(Set.of("client_credentials"))
.build();
// Create an instance of the provider, with in memory defaults
OIDCProvider provider = OIDCProvider.inMemoryDefaults(config);
// Create an oauth client
ClientRepository clientRepository = provider.getServiceRegistry().getClientRepository();
clientRepository.addClient(OAuthClient.builder()
.clientId("client")
.scopesSupported(Set.of(Scope.builder().name("profile").build()))
.clientSecret(SecretUtils.bcrypt("secret"))
.grantTypes(Set.of(GrantType.CLIENT_CREDENTIALS))
.build());
// build our application
JavalinJackson jsonMapper = new JavalinJackson();
jsonMapper.getMapper()
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
Javalin javalin = Javalin.create(conf -> {
conf.jsonMapper(jsonMapper);
});
javalin.post("/token", ctx -> {
String clientId = ctx.formParam("client_id");
String clientSecret = ctx.formParam("client_secret");
String grantType = ctx.formParam("grant_type");
String scope = ctx.formParam("scope");
ClientAuthentication auth = new ClientSecretPost(clientId, clientSecret);
TokenRequest tokenRequest = TokenRequest.builder()
.scopes(Set.of(scope))
.grantType(GrantType.byName(grantType))
.clientAuthentication(auth)
.build();
TokenResponse tokenResponse = provider.requestToken(tokenRequest);
ctx.json(tokenResponse);
});
javalin.start(8091);
}
}
You can now run this and test it with curl:
curl -X POST http://localhost:8091/token -d "client_id=client&client_secret=secret&grant_type=client_credentials&scope=profile"
You should get back a json response with an access token in it. Let’s break down what’s happened here.
- We create a provider configuration. This includes the base URL of the provider, and the grant types it supports.
String baseUrl = "http://localhost:8091";
// A barebones configuration
ProviderConfig config = ProviderConfig.builder()
.baseUrl(baseUrl)
.grantTypesSupported(Set.of("client_credentials"))
.build();
OIDCProvider provider = OIDCProvider.inMemoryDefaults(config);
- We create a client, and add it to the provider. This client has a client id of
client
, a client secret ofsecret
,
ClientRepository clientRepository = provider.getServiceRegistry().getClientRepository();
clientRepository.addClient(OAuthClient.builder()
.clientId("client")
.scopesSupported(Set.of(Scope.builder().name("profile").build()))
.clientSecret(SecretUtils.bcrypt("secret"))
.grantTypes(Set.of(GrantType.CLIENT_CREDENTIALS))
.build());
- We write a simple Javalin handler to accept a form and turn it into a token request, which we then hand off to the provider.
javalin.post("/token", ctx -> {
String clientId = ctx.formParam("client_id");
String clientSecret = ctx.formParam("client_secret");
String grantType = ctx.formParam("grant_type");
String scope = ctx.formParam("scope");
ClientAuthentication auth = new ClientSecretPost(clientId, clientSecret);
TokenRequest tokenRequest = TokenRequest.builder()
.scopes(Set.of(scope))
.grantType(GrantType.byName(grantType))
.clientAuthentication(auth)
.build();
TokenResponse tokenResponse = provider.requestToken(tokenRequest);
ctx.json(tokenResponse);
});
This should be mostly straightforward, with the only puzzle perhaps being client authentication. OAuth2 client authentication can take many forms, many of which deal directly with various networking protocols. Here we’ve used a simple client secret post, which would allow us to simply add the client id and secret to the form data. However, this is very much the exception rather than the rule. Basic authentication and private key JWT authentication involve dealing with HTTP headers, and these are heavily specific to the web framework being used. MTLS authentication involves dealing with an entirely different protocol layer. All of this is left for developers using OIDC4J to implement themselves, as covering all cases and frameworks would be onerous. The ClientAuthentication abstraction is there for you to construct.