DEV Community

inaba jun
inaba jun

Posted on

Writing Java library to build OAuth 2.0 Authorization Server / OpenID Connect Identity Provider

AzIdP4J

I'm writing AzIdP4J that the library to build Authorization Server and Identity Provider in Java.
The article talk about what I think while developing.

inabajunmr/azidp4j: Library for Java OAuth 2.0 Authorization Server & OpenID Connect Identity Provider

The library works as the following example.

// Convert HTTP Request query parameters to Map
var authorizationRequestQueryParameterMap =
        Map.of(
                "scope", "openid item:read",
                "response_type", "code",
                "client_id", "xyz-client",
                "redirect_uri", "https://client.example.com/callback",
                "state", "abc",
                "nonce", "xyz");
var authorizationRequest =
        new AuthorizationRequest(
                "inabajun", // authenticated user subject. Application needs to implements user authentication. If no user is authenticated, specify null.
                Instant.now().getEpochSecond(),
                Set.of("openid", "item:read"), // User consented scopes. Application needs to implements user consent.
                authorizationRequestQueryParameterMap);

// Authorization Request
var response = azIdP.authorize(authzReq);

// Library returns what should do next so implements application's behavior.
switch (response.next) {
    case redirect -> {
        // Redirect to response.redirect.redirectTo
    }
    case additionalPage -> {
        // Implements additional process like login or consent or...
    }
    case errorPage -> {
        // Error happend but can't redirect to redirct_uri
    }
}
Enter fullscreen mode Exit fullscreen mode

When you want Authorization server / Identity provider

When we want Authorization server / Identity provider, we can select the following patterns.

  • Develop them by yourself
  • Develop them with libraries
  • Use a service that works as an Authorization server / Identity provider in conjunction with your application
  • Using standalone Authorization server / Identity provider

In the case of scratch development, we need to write many sources. We need to understand the specifications. We need to continue learning about new specifications and trends...

I often feel to develop user experiences myself but delegating processes about protocols like OAuth 2.0 / OIDC to a library. So I'm developing the library to build OAuth 2.0 Authorization Server / OIDC Identity Provider in Java. (Because Spring Security OAuth was EOL already.)

Policies

I think the most important thing about libraries is the interface.
I decided on the following policies to consider interfaces.

  • Don't depend on a specific framework
  • Easy to issue Access token / ID token
  • Applications can implement user experiences by themselves
  • No datastore implementations

Don't depend on a specific framework

OAuth 2.0 / OpenID Connect depends on HTTP as a specification. So if a library can be used without any additional implementations, the library needs to accept HTTP requests directly.

But there are many Java web application frameworks and I want to implement a library that can be used for any framework.

So AzIdP4J has more abstract interfaces than accepting HTTP requests directly.

For example, token request / token response is expressed as following examples.

Spring based example implementation is like this.

@PostMapping(value = "token", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ResponseEntity<Map> tokenEndpoint(@RequestParam MultiValueMap<String, Object> body) {

    // client authentication by the application...
    // convert HTTP request query parameters to Map
    var request = new TokenRequest(authenticatedClientId, body.toSingleValueMap();
    // Token Request
    var response =
            azIdP.issueToken(request);

    // convert AzIdP4J resposne to HTTP response
    return ResponseEntity.status(response.status).body(response.body);
}
Enter fullscreen mode Exit fullscreen mode

On the other hand, AzIdP4J doesn't support functions within the protocol that be highly dependent on the framework. For example, I think many frameworks have functions to authenticate a client and authorize by bearer token so AzIdP4J doesn't support them.

Easy to issue Access token / ID token

I decided to make it easy to implement issuing each token by Authorization Code Flow. If supporting many functions makes interfaces complicated, AzIdP4J doesn't support them.

For example, if AzIdP4J supports the UserInfo endpoint, AzIdP4J needs to know Users but it makes interfaces complicated so I don't support it yet.

Applications can implement user experiences by themselves

Behavior after authorization request doesn't close in protocols. When the authorization request has prompt=login, the application needs to show a login page but how to provide authentication user experiences are out of the scope of these specifications. The application needs to provide the application-specific authentication flow. AzIdP4J wants not to affect user experiences like user authentication, consent, etc...

So AzIdP4J provides the following interface.

// authzReq has authenticated user subject but can expresses not authenticated.
var response = azIdP.authorize(authzReq);
// Library returns what should do next so implements application's behavior.
switch (response.next) {
    case redirect -> {
        // Redirect to response.redirect.redirectTo
    }
    case additionalPage -> {
        // Implements additional process like login or consent or...
    }
    case errorPage -> {
        // Error happend but can't redirect to redirct_uri
    }
Enter fullscreen mode Exit fullscreen mode

Authorization Request Document

No datastore implementations

I like ory/fosite interfaces so AzIdP4J imitates them. AzIdP4J needs to manage tokens so it provides only interfaces and an Application needs to implement them.

Things not to support

When I implement protocols, I become to wish to implement any specifications but I decided on things that don't support to control my motivation.

I decided first milestones.

  • Make AzIdP4J work at least
  • Writing documents with concrete examples

For the milestone, I removed the following things at the first milestone.

  • Doesn't support all of the core specification
    • For example, Request Object
    • Doesn't support specifications that I don't understand concrete demands.

It's difficult what specifications should be supported.

Make AzIdP4J work at least

First of all, I implemented accepting authorization requests and issuing access tokens and ID tokens via the token endpoint. I often test OICD Conformance Test against my implementation while implementing. This made progress visible so It was good to continue my motivation. The conformance test revealed some specifications that I can't understand correctly also.

Writing documents with concrete examples

I understood some of the specifications through conformance tests, writing sources and so one. But I can't understand whether the library's interfaces are good or bad. I want feedback about the library so I wrote documents with concrete examples. I'll be glad if you provide feedback.

AzIdP4J Documentation

Development process

For continuous checking of the policy "Don't depend on a specific framework", I implemented 2 types of sample identity frameworks.

The approach helped my decision on interfaces. For example, when I considered whether the application with the library can implement client authentication or not, multiple implementations help my consideration.

The library development with client implementations is better than just with test cases. Cases that I can't discover from the latter sometimes appeared.

Conclusion

I'm writing AzIdP4J that the library to build Authorization Server and Identity Provider in Java.
The article talked about what I thought while developing.

inabajunmr/azidp4j: Library for Java OAuth 2.0 Authorization Server & OpenID Connect Identity Provider

The library works as the following example.

// convert HTTP Request query parameters to Map
var authorizationRequestQueryParameterMap =
        Map.of(
                "scope", "openid item:read",
                "response_type", "code",
                "client_id", "xyz-client",
                "redirect_uri", "https://client.example.com/callback",
                "state", "abc",
                "nonce", "xyz");
var authorizationRequest =
        new AuthorizationRequest(
                "inabajun", // authenticated user subject. Application needs to implements user authentication. If no user is authenticated, specify null.
                Instant.now().getEpochSecond(),
                Set.of("openid", "item:read"), // User consented scopes. Application needs to implements user consent.
                authorizationRequestQueryParameterMap);
var response = azIdP.authorize(authzReq);
// Library returns what should do next so implements application's behavior.
switch (response.next) {
    case redirect -> {
        // Redirect to response.redirect.redirectTo
    }
    case additionalPage -> {
        // Implements additional process like login or consent or...
    }
    case errorPage -> {
        // Error happend but can't redirect to redirct_uri
    }
}
Enter fullscreen mode Exit fullscreen mode

I hope your feedbacks. Thank you.

Top comments (0)