Simple JWT Token authentication with Quarkus

Dmytro Chaban
Quarkify
Published in
6 min readMay 12, 2020

Every significant app will have security. And you probably opened this article because you’re looking for some simple yet durable solution. What can be simpler yet good enough as JWT Token authorization? Official Quarkus website already covered this topic, but I’ll show the most popular use-case for it, that is, generation of the token when the user logs in. I’ll show you a more abstract solution that you can plug-in to any app and generate a token with a single line of code in your /login method.

I also covered this topic at my Quarkus course in Security section. This articles basically repeats same lesson with additional simplicity

User flow

We’ll take the most usual use case: Users open your app, users register, then they do log in, receive the token, and after that use your app as they please.

Initial setup

You can init empty project from scratch by using next command:

mvn io.quarkus:quarkus-maven-plugin:1.4.2.Final:create \
-DprojectGroupId=tech.donau.quarkify \
-DprojectArtifactId=jwt-auth \
-DclassName="tech.donau.quarkify.ExampleResource" \
-Dpath="/hello" \
-Dextensions="resteasy-jsonb, jwt, jdbc-h2, hibernate-orm-panache"

Or, if you already have some project, you can add next dependencies into pom.xml

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jsonb</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-jwt</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-h2</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm-panache</artifactId>
</dependency>

Complete working solution, as always, available at our github.

Static classes

Let’s start by adding TokenUtils class, it's independent so you can include it into any project.

You’re only interested in generateTokenString method. This method will be executed by our future TokenService. You can trace what it's doing, but basically, from an abstraction point of view, this method will encode our JwtClaims object, sign it with a private key that we will generate in next steps(one command line), and return to us it back as a string that we can pass to the user.

Other methods can be handy for some specific use-case, we won’t touch them in this article.

Token service

Now, once we have prepared class that will handle all underlying logic, we can create service that will be used by our services and resources.

JWT embeds idea that every user has role. You can, for example, have user, service and admin roles. Users are basic users of your app, they only have access to user endpoints. We can say that services are bots or applications that have access to limited amount of endpoints. One example can be that service can only upload data, it can’t read or delete it. And admin, of course, is god, he has access to such endpoints that are not available to simple users.

Let’s create a static class with constants for them. This will help you in future if you accidentally will put user instead of User for some endpoint and all the users won't have the ability to execute this endpoint.

And now, let’s create final class, which we’ll use to generate token

You can see that we have generateUserToken() method that calls generateToken(). The last method executes our TokenUtils.generateTokenString() method, which returns the token.

Private and Public keys

In order to JWT to work, we need to sign it with private key. Without this step anybody can just pass fake data to our Quarkus app, and it will think that it’s valid credentials. This way anybody can use admin endpoints, for example. Luckly get private and public key is super easy:

openssl genrsa -out publicKey.pem
openssl pkcs8 -topk8 -inform PEM -in publicKey.pem -out privateKey.pem -nocrypt
openssl rsa -in publicKey.pem -pubout -outform PEM -out publicKey.pem

This will create two files, which you can move into src/resources folder. Beware that if you loose this files, users that already generated tokens will be declined in authentication. I would suggest to keep them somewhere safe. Don't share them with anyone as well.

Enable JWT Security

When you moved publicKey.pem and privateKey.pem into src/resources, TokenService will be already usable, but we also need to pass some application properties in order for Quarkus to know where to look for our publicKey.pem. Put next data into application.properties

quarkus.smallrye-jwt.enabled=true
mp.jwt.verify.publickey.location=publicKey.pem
mp.jwt.verify.issuer=DonauTech

Adding persistency

Let’s add User object for our whole article to be usable. I'll use h2 database with Hibernate and Panache to make it as small as possible.

Firstly, let’s create User, as in next example. Keep in mind that it's super simple example, even without password encryption, this is not a production solution 🙂

Let’s also add some more parameters into application.properties

quarkus.datasource.url=jdbc:h2:file:./test quarkus.datasource.driver=org.h2.Driver quarkus.datasource.username=test quarkus.datasource.password=test quarkus.hibernate-orm.database.generation=update quarkus.hibernate-orm.dialect=org.hibernate.dialect.H2Dialect

This is a super simple file database. It’ll be located at target/test.db. We use update, which will update schema without deletion of it on restart

Registration and login

We now ready to finally do registration and login. Ideally, we can have UserService, but for this example I just put everything into UserResource

There’s nothing special here.

1. We have /register method that just persists the user itself into the database.
2. We have /login method that checks if data is correct, and after that generates a token back to user. Here's a homework for you: make it return a {"token": data} structure so it's easy to use from javascript.

Secure regular endpoints

Security is not a security if it secures nothing. Let’s create few endpoints to test our token. We already have ExampleResource, let's expand it a little bit.

Let’s go one method at a time:

  1. String hello() method is just a default method with @PermitAll annotation. This annotation says that anyone can access this endpoint. Removing it won't change behavior in JWT case.
  2. User me() can only be executed by either User or Service, as @RolesAllowed({Roles.USER, Roles.SERVICE}) says.
  3. String adminTest() can only be executed by Admin. If you try to call it with a User you'll get 403 Unauthenticated response.
  4. String nothing() won't be accessible by anyone.

Test time

Now it’s time to do full test cycle and see how it’ll work. I’ll use curl commands with postman screenshots.

curl --location --request POST 'http://localhost:8080/users/register' \
--header 'Content-Type: application/json' \
--data-raw '{
"login": "testuser",
"email": "testuser@gmail.com",
"password": "testtest"
}'

This will return same result, but you’ll also have your id.

Now let’s log into our application by getting JWT token that we can later use in any other endpoint

curl --location --request GET 'http://localhost:8080/users/login?login=testuser&password=testtest'

Let’s try to execute /hello without any token, it should work

curl --location --request GET 'http://localhost:8080/hello/'

Now, put our token into header so that key is Authorization and value is Bearer $token

Now we can execute /hello/me endpoint, you should see user object in output

curl --location --request GET 'http://localhost:8080/hello/me' \
--header 'Authorization: Bearer $YOURTOKEN'

You can see drawback of storing password as a plain text here. Ideally you shouldn’t return password at all.

Let’s try to execute /hello/admin endpoint

curl --location --request GET 'http://localhost:8080/hello/admin' \
--header 'Authorization: Bearer $YOURTOKEN'

As expected, simple user won’t be able to access admin endpoints. Try generating admin token yourself and executing this endpoint

And last endpoint, /hello/void, which won't work for anyone.

curl --location --request GET 'http://localhost:8080/hello/void' \
--header 'Authorization: Bearer $YOURTOKEN'

In conclusion

Now you know embeddable security solution that you can re-use in any project. Of course, there are many other options and this JWT auth doesn’t fit all. But for starters and simple projects, this solution can be really easy, without adding extra services just to handle the basic flow.

Originally published at https://quarkify.net on May 12, 2020.

--

--

Dmytro Chaban
Quarkify

Software Engineer, addicted to productivity and automatization