How to Implement Nuxt.js/Vue.js OAuth2 Authentication With an External REST-API Server (based on Vert.x/Kotlin) and Keycloak 🐬
Johannes Lichtenberger
Posted on November 17, 2019
Introduction
Authentication is hard. Therefore it's best to delegate authentication to a dedicated Software. In our case, we decided to use Keycloak.
We want to build a Nuxt.js based front-end for SirixDB, a temporal document store, which can efficiently retain and query snapshots of your data. A non-blocking, asynchronous REST-API is provided by an HTTP-Server. We decided to use Kotlin (heavy use of Coroutines) and Vert.x to implement the API-Server.
Authentication via OAuth2
OAuth2 specifies several so-called flows. For browser-based applications, the Authorization Code Flow is the best and most secure flow, which we'll use.
💚 OAuth2 Authorization Code Flow with Nuxt.js
We have a Workflow, where only ever the SirixDB HTTP-Server is interacting with Keycloak directly (besides redirects to the Node.js server). Thus, our front-end just has to know two routes of the SirixDB HTTP-Server: GET /user/authorize and POST /token.
In general, our workflow is as follows:
An authentication middleware controls if users should be redirected to a /login route to login in the first place
The /login route has a simple Button, which issues a request to the SirixDB HTTP-server. Nuxt.js generates a unique, unguessable state and a redirect_uri, which Nuxt.js sends to the GET /user/authorize route as URL parameters.
The HTTP-Server redirects to a login-page of Keycloak and sends the two parameters as well
Once a user correctly fills in his credentials, Keycloak redirects the browser to the given redirect_url, which Nuxt.js sends in the first place (and the SirixDB HTTP-Server)
On the Node.js server, the Nuxt.js based front-end, a callback route is addressed by the redirect-URL from Keycloak
Nuxt.js then extracts a URL parameter code and checks the state parameter for validity
Next, Nuxt.js sends a POST HTTP-request to the /token endpoint on the SirixDB HTTP-Server with the code parameter, the redirect_uri again, which is the same callback route. Additionally, it sends a response_type which we set to code, such that Nuxt.js expects a JWT access token
The SirixDB HTTP-Server then exchanges the given code with a JWT access token from Keycloak and sends it in the HTTP response to the Nuxt.js based front-end
Note that we can simplify this workflow if we are in the universal mode (not SPA). The Node.js server from Nuxt.js could also directly communicate with Keycloak, as we'll see later on. In this setup, the SirixDB HTTP-Server will only check authorization on its routes based on the issued JWT-tokens. However, this way, the front-end doesn't need to know that it's Keycloak and the host/ports and endpoint details. Furthermore, we'll see that Nuxt.js doesn't work with Keycloak out of the box.
👾 Nuxt.js Setup
In the Nuxt.js configuration file nuxt.config.js we have to add the following modules:
https://localhost:9443 is the host/port where the SirixDB HTTP-Server is listening.
Per default, our Nuxt.js configuration activates the authentication middleware on all routes. If the user is not authenticated, the first step is initiated, and the auth module from Nuxt.js redirects the user to the GET /login route.
We'll define a straightforward login page:
<template><div><h3>Login</h3><el-buttontype="primary"@click="login()">Login via Keycloak</el-button></div></template><script lang="ts">import{Component,Prop,Vue}from"vue-property-decorator";@ComponentexportdefaultclassLoginextendsVue{privatelogin():void{this.$auth.loginWith('keycloak')}}</script><style lang="scss"></style>
To define the right TypeScript types to use this.$auth we'll have to add
Note that usually, Nuxt.js specifies the redirect-URI, in which case the SirixDB HTTP-server reads it from the URL query parameters.
The HTTP-Server uses the following extension function, to provide coroutine handlers, whereas the suspending functions run on the Vert.x event loop:
/**
* An extension method for simplifying coroutines usage with Vert.x Web routers.
*/privatefunRoute.coroutineHandler(fn:suspend(RoutingContext)->Unit):Route{returnhandler{ctx->launch(ctx.vertx().dispatcher()){try{fn(ctx)}catch(e:Exception){ctx.fail(e)}}}}
The GET /user/authorize route (step 2). The browser will be redirected to the Keycloak login page.
After providing the credentials, the Browser is sent back to the redirect_uri, (the /callback route), with the given state (generated by Nuxt.js in the first place). Then the auth module of Nuxt.js extracts the state and code from the URL query parameter. If the state is the same as it generated, it proceeds to POST the code and stores, the redirect_uri again, and the response_type as form parameters.
The POST /token route (step 7):
post("/token").handler(BodyHandler.create()).coroutineHandler{rc->try{valdataToAuthenticate:JsonObject=when(rc.request().getHeader(HttpHeaders.CONTENT_TYPE)){"application/json"->rc.bodyAsJson"application/x-www-form-urlencoded"->formToJson(rc)else->rc.bodyAsJson}valuser=keycloak.authenticateAwait(dataToAuthenticate)rc.response().end(user.principal().toString())}catch(e:DecodeException){rc.fail(HttpStatusException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(),"\"application/json\" and \"application/x-www-form-urlencoded\" are supported Content-Types."+"If none is specified it's tried to parse as JSON"))}}privatefunformToJson(rc:RoutingContext):JsonObject{valformAttributes=rc.request().formAttributes()valcode=formAttributes.get("code")valredirectUri=formAttributes.get("redirect_uri")valresponseType=formAttributes.get("response_type")returnJsonObject().put("code",code).put("redirect_uri",redirectUri).put("response_type",responseType)}
The SirixDB HTTP-Server retrieves a JWT token from Keycloak and sends it back to the front-end.
Afterward, Nuxt.js stores the token in its session, the store, and so on.
Finally, Axios has to send the token for each API request it does in the Authorization-Header as a Bearer token. We can retrieve the token via this.$auth.getToken('keycloak').
Note that instead of the indirection using the SirixDB HTTP-Server, Nuxt.js/Node.js could interact with Keycloak directly and the SirixDB HTTP-Server then only validates the JWT-tokens.
In that case the nuxt.config.js keycloak auth object looks as follows:
In this case we need to add http://localhost:3005 to the allowed Web Origins in Keycloak as we'll see in the next section.
However, I couldn't get this to work, as the auth module from Nuxt.js somehow doesn't send the client_secret to the Keycloak token-endpoint:
error: "unauthorized_client"
error_description: "Client secret not provided in request"
💚 Setting up Keycloak
Setting up Keycloak can be done as described in this excellent Tutorial. The following description is a short SirixDB summary (you can skip some parts by using SirixDBs docker-compose file). However, it should be almost identical to the Keycloak setuo of other projects.
In short :
Open your browser. URL: http://localhost:8080
Login with username admin and password admin to access Keycloaks web configuration interface
Create a new realm with the name sirixdb
Go to Clients => account
Change client-id to sirix
Make sure access-type is set to confidential
Go to Credentials tab
Put the client secret into the SirixDB HTTP-Server configuration file (posted above). Change the value of client.secret to whatever Keycloak set up.
The standard flow on the settings tab must be enabled.
Set the valid redirect URIs to http://localhost:3005/* or port 3000 or wherever your Nuxt.js application runs
Make sure to set the right values for Web Origins to allow CORS from these domains
Conclusion
Setting up everything to work together brought about some headaches. One simplification would be to let Nuxt.js do all the authentication in the first place, and let the external API server check the tokens.
Let me know if this article helps or if I made the whole authorization process too complicated.
Regarding SirixDB and the front-end I'd love to get some input or even contributions, that would be the most remarkable thing :-) I'm a backend engineer and I'm currently learning Nuxt.js/Vue.js and TypeScript as well as D3 in my spare time for this project. It's a green field project, so we can use the Vue.js Composition API for instance. 🐣
And if you like the project, you might share it on twitter and so and spread the word!? 🙈
SirixDB is an an embeddable, bitemporal, append-only database system and event store, storing immutable lightweight snapshots. It keeps the full history of each resource. Every commit stores a space-efficient snapshot through structural sharing. It is log-structured and never overwrites data. SirixDB uses a novel page-level versioning approach.
An Embeddable, Bitemporal, Append-Only Database System and Event Store
Stores small-sized, immutable snapshots of your data in an append-only manner. It facilitates querying and reconstructing the entire history as well as easy audits.
"Remember that you're lucky, even if you don't think you are because there's always something that you can be thankful for." - Esther Grace Earl (http://tswgo.org)
We want to build the database system together with you. Help us and become a maintainer yourself. Why? You may like the software and want to help us. Furthermore, you'll learn a lot. You may want to…
SirixDB Web frontend - An Evolutionary, Versioned, Temporal NoSQL Document Store
Store and query revisions of your data efficiently
"Remember that you're lucky, even if you don't think you are, because there's always something that you can be thankful for." - Esther Grace Earl (http://tswgo.org)
It'll provide several interaction possibilities to store, update and query databases in SirixDB. Furthermore the front-end will provide interactive visualizations for exploring and comparing revisions of resources stored in SirixDB based on different views.