wissel.net

Usability - Productivity - Business - The web - Singapore & Twins

Now we are token - Authorization using JSON Web Token in Domino


After having Vert.x and Domino co-exist, the door opens for a few interesting applications of the new found capabilities. One sticky point in each application landscape is authentication and authorization. This installment is about authorization.
The typical flow:
  1. you access a web resource
  2. provide some identity mechanism (in the simplest case: username and password)
  3. in exchange get some prove of identity
  4. that allows you to access protected resources.
In Basic authentication you have to provide that prove every time in form of an encoded username/password header. Since that limits you to username and password, all other mechanism provide you in return for your valid credentials with a "ticket" (technically a "Bearer Token") that opens access.
I tend to compare this with a movie theater: if you want to enter the room where the movie plays you need a ticket. The guy checking it, only is interested: is it valid for that show. He doesn't care if you paid in cash, with a card, got it as a present or won in a lucky draw. Did you buy it just now, or online or yesterday, he doesn't care. He cares only: is it valid. Same applies to our web servers.
In the IBM world the standard here is an LTPA token that gets delivered as cookie. Now cookies (besides being fattening) come with their own little set of trouble and are kind of frowned upon in contemporary web application development.
The current web API token darling is JSON Web Token (JWT). They are an interesting concept since they sign the data provided. Be clear: they don't encrypt it, so you need to be careful if you want to store sensitive information (and encrypt that first).

Now how to put that into Domino?

The sequence matches the typical flow:
  1. User authenticates with credentials
  2. server creates a JWT
  3. stores JWT and credentials in a map, so when the user comes back with the token, the original credentials can be retrieved
  4. delivers JWT to caller
  5. Caller uses JWT for next calls in the header
It isn't rocket science to get that to work.First some preliminary steps. Since we use cryptographic features, we need a keystore. You can easily create one using keytool. To make my life easier I created a small script to generate all I need:

# !/bin/sh
# Generate a keystore file with a given password
keytool -genseckey -keystore keystore.jceks -storetype jceks -storepass $1 -keyalg HMacSHA256 -keysize 2048 -alias HS256 -keypass $1
keytool -genseckey -keystore keystore.jceks -storetype jceks -storepass $1 -keyalg HMacSHA384 -keysize 2048 -alias HS384 -keypass $1
keytool -genseckey -keystore keystore.jceks -storetype jceks -storepass $1 -keyalg HMacSHA512 -keysize 2048 -alias HS512 -keypass $1
keytool -genkey -keystore keystore.jceks -storetype jceks -storepass $1 -keyalg RSA -keysize 2048 -alias RS256 -keypass $1 -sigalg SHA256withRSA -dname "CN=,OU=,O=,L=,ST=,C=" -validity 360
keytool -genkey -keystore keystore.jceks -storetype jceks -storepass $1 -keyalg RSA -keysize 2048 -alias RS384 -keypass $1 -sigalg SHA384withRSA -dname "CN=,OU=,O=,L=,ST=,C=" -validity 360
keytool -genkey -keystore keystore.jceks -storetype jceks -storepass $1 -keyalg RSA -keysize 2048 -alias RS512 -keypass $1 -sigalg SHA512withRSA -dname "CN=,OU=,O=,L=,ST=,C=" -validity 360
keytool -genkeypair -keystore keystore.jceks -storetype jceks -storepass $1 -keyalg EC -keysize 256 -alias ES256 -keypass $1 -sigalg SHA256withECDSA -dname "CN=,OU=,O=,L=,ST=,C=" -validity 360
keytool -genkeypair -keystore keystore.jceks -storetype jceks -storepass $1 -keyalg EC -keysize 256 -alias ES384 -keypass $1 -sigalg SHA384withECDSA -dname "CN=,OU=,O=,L=,ST=,C=" -validity 360
keytool -genkeypair -keystore keystore.jceks -storetype jceks -storepass $1 -keyalg EC -keysize 256 -alias ES512 -keypass $1 -sigalg SHA512withECDSA -dname "CN=,OU=,O=,L=,ST=,C=" -validity 360

you call the function with the password you would like to use: makekeys Sup3rs3cr3t. In vert.x you provide a route protection (e.g. /api) and a login route. This is done quite easily. To get the whole picture about vert.x and verticles, you might want to check the tutorial first (comes in JavaScript flavour too).

// Getting the parameters from the environment, so it's not hardcoded
final String jwtPassword = System.getenv("JWT_PASSWORD");
final String jwtKeyStore = System.getenv("JWT_KEYSTORE");
// The JWT provider
final JsonObject keyStore = new JsonObject()
                                .put("type", "jceks")
                                .put("path", jwtKeyStore)
                                .put("password",jwtPassword);

// Needs to be instance level.. this.jwt
JWTAuth jwt = JWTAuth.create(vertx, new JsonObject().put("keyStore", keyStore));

// Setting up the routes
this.router = Router.router(vertx);

// Homepage - no authentication
this.router.route("/").handler(this::rootHandler);

// Body handling
router.route().handler(BodyHandler.create());

// Secured routes, except the login root
router.route("/api").handler(JWTAuthHandler.create(jwt, "/api/login"));
router.post("/api/login").handler(this::loginHandler);

In case you wonder about the syntax. That's Java8 goodness. The missing piece now is the loginHandler. There we face an extra challenge. Rule number one for all event loop driven enviornments (like NodeJS too): You do not block the event queue. Now an authentication can take a little longer, so we need to wrap that call into a future (Java's way of saying: I promise. Sounds much more complicated than it actually is:

public void loginHandler(final RoutingContext ctx) {

    final JsonObject user = ctx.getBodyAsJson();
    final HttpServerResponse response = ctx.response();
    final String userName = user.getString("username");
    final String password = user.getString("password");

    this.vertx.executeBlocking(future -> {
        final String execResult = this.validateNotesUser(userName, password);
        if (execResult != null) {
            future.complete(execResult);
        } else {
            future.fail("Failure");
        }
    }, res -> {
        if (res.failed()) {
            response.setStatusCode(401).end("Sorry, auth didn't work");
        } else {
            response.setStatusCode(200).end(res.result());
        }
    });
}

public String validateNotesUser(final String userName, final String password) {
    String result = null;
    NotesThread.sinitThread();
    try {
        // Throws an error if the user is not valid
        final session = NotesFactory.createSession((String) null, userName, password);
        final JWTOptions jwtOptions = new JWTOptions().setExpiresInSeconds(86400);
        result = this.jwt.generateToken(new JsonObject(), jwtOptions);
    } catch (final Exception e) {
        result = null;
    }
    NotesThread.stermThread();
    return result;
}

I skipped the part where token and user gets stored in a lookup map. That's an story for another time. Now the result coming back from successfully posting to /api/login is a bearer token. This token needs to be send in all future requests as a header: Authorization: Bearer token. There will be more about vert.x and Domino

Posted by on 24 February 2016 | Comments (1) | categories: IBM Notes JWT vert.x

Comments

  1. posted by HenrikGr on Thursday 03 March 2016 AD:

    Thanx for your pointer att Stackoverflow to use Ektorp Java library to export data from Domino to CoucheDb.

    That of course made me visiting your blog and this post was also very interesting.

    I have one simple question though.

    Do you think I do wrong if I try to use Node.js runtime as an API proxy towards Domino Access Services?

    That is what I'm working on right now, but find it difficult with seesion authentication and to post a login for to the names.nsf?login.

    Anothring thing is of course URL mapping, a REST JSON proxy.

    I have hard to get all pieces together and start to think I tries something I just should not. :-)

    Best Regards Henrik