Using JWTs To Secure Your App

When writing client/server applications, it is often necessary that a service restricts data to a set of trusted users. In order to do this, the service needs to know the identity of the person making a given request — determined in an initial step called authentication — and whether that user is allowed to access the data provided by that service; which is referred to as authorization. There are quite a few ways to implement authentication and authorization, one of the easier approaches that we’ve employed lately is to make use of JSON Web Tokens (often pronouned ‘jot’ and abbreviated as JWTs)

Overview

At a high level, JWT makes use of public key encryption where a central authority uses their private key to sign a JSON payload containing identifying information as well as a specification for the timeframe during which that token is valid. This JWT can then be authenticated by any service that has a copy of the corresponding public key.

Note that since a JWT is often used as a standalone means of authenticating a user, anyone possessing an individual’s token can make requests as that user. For this reason, care should be given to how JWTs are transmitted (we recommend using https to secure connections) and stored (if a host is shared amongst several users).

The code associated with this blogpost can be found in the accompanying online repository .

Token creation

The JWT specification is public and anyone is free to implement it. As fans of open-source, we use the auth0 Java implementation that we found via jwt.io‘s list of implementations. I recommend bookmarking jwt.io as their online token decoder comes in handy for verifying the contents of a given token. As an example, here’s what jwt.io displays for one of our test tokens for this example:

Step 1: Generating the keypair to use

This was the most difficult part of integrating JWTs into our platform — which is a testament to how easy the rest of the steps are. Java expects public/private key pairs to be in a specific format (that is, PKCS#8 format) but the commonly used openssl utility generates keys in a slightly different format (that is, PKCS#1). This mismatch requires you to convert the openssl output after generating your keys for a given application or project.

1) Generate a private RSA512 key in PKCS#1 format

openssl genrsa -f4 -out id_rsa 4096

2) Export the public key

openssl rsa -in id_rsa -outform PEM -pubout -out id_rsa.pub

3) Export a private key to PKCS#8 format

openssl pkcs8 -topk8 -inform pem -in id_rsa -outform PEM -nocrypt -out id_rsa.priv

Step 2: Use the keys to generate a JWT

We’ve put together a sample Java application that creates a JWT and then uses a private key to sign it. There are instructions for building & running the code as well as sample invocations in the repo.

Example REST server

JWTs are used for authenticating a user and can contain information that can then be used for authorization. We will provide examples of both in our REST server code, which is an augmentation of the example REST server from another online tutorial.

Using the aforementioned REST server code as a starting point (and updating the generated pom.xml to specify Java 1.8 instead of the default 1.7), we then use the auth0 JWT library  to provide JWT functionality. See the functions in JWTUtil.java and the  userIsAdmin()function in CustomerService.java.

In this simple example, we have the following endpoints:

  • /status
    • publicly accessible endpoint that returns “Status: ok”
      • This is a common pattern that allows anyone (or an automated service) to confirm that the server is running
  • /all
    • token-protected endpoint for listing some data for all of the users
  • /{id}
    • token-protected endpoint for listing all of the information on a specific user
      • Administrators will see more information than non-administrators
      • The list of groups that should be considered administrators is defined in the application code (in the adminGroups_variable  in CustomerService.java) separately from the JWT functions (located in JWTUtil.java).

To see the code in action, start up the REST server like so:

bash-3.2$ pwd
/Users/buck/git/public-code-samples/blogposts/2018_aug_using_jwts/rest_server/jersey-service
bash-3.2$ mvn exec:java
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------com.okera:jersey-jwt-server---------------------
[INFO] Building jersey-jwt-server 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] exec-maven-plugin:1.2.1:java (default-cli) > validate @ jersey-jwt-server 
[INFO]
[INFO] exec-maven-plugin:1.2.1:java (default-cli) < validate @ jersey-jwt-server
[INFO]
[INFO]
[INFO] --- exec-maven-plugin:1.2.1:java (default-cli) @ jersey-jwt-server ---
Sep 01, 2018 2:13:04 PM org.glassfish.grizzly.http.server.NetworkListener start
INFO: Started listener bound to [localhost:8080]
Sep 01, 2018 2:13:04 PM org.glassfish.grizzly.http.server.HttpServer start
INFO: [HttpServer] Started.
Sep 01, 2018 2:13:04 PM com.example.rest.Main main
INFO: initializing - trying to load configuration file ...
Jersey app started with WADL available at http://localhost:8080/myapp/application.wadl
Hit enter to stop it...

Then, from another terminal, issue a curl GET call against the /status endpoint:

 bash-3.2$ curl -X GET http://localhost:8080/myapp/customers/status
Status: ok
bash-3.2$

To list all of the customers in the system, we hit the /all endpoint:

bash-3.2$ curl -X GET http://localhost:8080/myapp/customers/all
You are not authorized to view this content
bash-3.2$

But, as we mentioned, the /all endpoint is protected, so we need to pass a token in the header, like so:

bash-3.2$ curl -X GET -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiJ9.eyJzdWIiOiJqYW5lIiwiaXNzIjoib2tlcmEuY29tIiwiZ3JvdXBzIjpbIm5vbl9hZG1pbiIsImJpcmRfb3duZXIiXSwiZXhwIjoxNTk0MzEyOTY4fQ.OvTlZCAOKoCG5jp1tPtwB7TXMSRfDkNhyDcEckj8-PQKnXfnw0CkgDS-URmmb58dNpZbdckFoDqYzslsXS3OeNuQt-Hx-qsaY5anZBRaGbZAn-3bLCWxhQICptftTTBcNghilWKlVWvrD1zLA1eLfAypmoRhzLbuYuBomVt9Af0rkWH9bpS-V7rA6Z5qNhl72WqRsZqpVglaSrJxnbubV3eFOTYehhgje_BVJg9Ygu9t0inAqI0BvhHEPP47siSLqvp7Ss27TfT7uQmUiEf0YyjBtD7XEVN-k7vD6G4eRH3LrbQCimFJ73ayykhL9wlV36h_Zs4EKyJhIGoVspcY-tw0ZxZJSbPxQ44cwfJzURLv_uAgehRn3hXf9if_fgEC6YNIWIbdG4D7p5AUg7SYTm2uoaHutuxVj6_jRogPw-6mUzyKvlaZir_OQpyX_oYZmW2eSaDxIzr3Mwdoz2_A1db4FrIlUnr2TAM0T4vyj9iMCp4kp_9HZWbNVeXXPiJWLAg1Q5BNcx_JhWszkGq2DpwBQu73xSdXwmDRYSkYk6gwRCm6DtG_7wThaBVNN5bX3KFfPO0Snlrda0FZlrhTuHeVR5aqTVimYY-xXsU3GzMQQXuQRHpf_zxyNlwNSPtEjR3gIIYskaYKJRoroPx8E_Px29gIB6Mz8xFRqgeHF4c' http://localhost:8080/myapp/customers/all
---Customer List---
ID: 100 First: George Last: Washington
EMail: gwash@example.com
City: Mt Vernon State: VA Birthday 1732-02-23
ID: 101 First: John Last: Adams
EMail: jadams@example.com
City: Braintree State: MA Birthday 1735-10-30
ID: 102 First: Thomas Last: Jefferson
EMail: tjeff@example.com
City: CharlottesVille State: VA Birthday 1743-04-13
ID: 103 First: James Last: Madison
EMail: jmad@example.com
City: Orange State: VA Birthday 1751-03-16
ID: 104 First: James Last: Monroe
EMail: jmo@example.com
City: New York State: NY Birthday 1758-04-28
bash-3.2$

To list the information for one specific user, you can issue this call

bash-3.2$ curl -X GET -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiJ9.eyJzdWIiOiJqYW5lIiwiaXNzIjoib2tlcmEuY29tIiwiZ3JvdXBzIjpbIm5vbl9hZG1pbiIsImJpcmRfb3duZXIiXSwiZXhwIjoxNTk0MzEyOTY4fQ.OvTlZCAOKoCG5jp1tPtwB7TXMSRfDkNhyDcEckj8-PQKnXfnw0CkgDS-URmmb58dNpZbdckFoDqYzslsXS3OeNuQt-Hx-qsaY5anZBRaGbZAn-3bLCWxhQICptftTTBcNghilWKlVWvrD1zLA1eLfAypmoRhzLbuYuBomVt9Af0rkWH9bpS-V7rA6Z5qNhl72WqRsZqpVglaSrJxnbubV3eFOTYehhgje_BVJg9Ygu9t0inAqI0BvhHEPP47siSLqvp7Ss27TfT7uQmUiEf0YyjBtD7XEVN-k7vD6G4eRH3LrbQCimFJ73ayykhL9wlV36h_Zs4EKyJhIGoVspcY-tw0ZxZJSbPxQ44cwfJzURLv_uAgehRn3hXf9if_fgEC6YNIWIbdG4D7p5AUg7SYTm2uoaHutuxVj6_jRogPw-6mUzyKvlaZir_OQpyX_oYZmW2eSaDxIzr3Mwdoz2_A1db4FrIlUnr2TAM0T4vyj9iMCp4kp_9HZWbNVeXXPiJWLAg1Q5BNcx_JhWszkGq2DpwBQu73xSdXwmDRYSkYk6gwRCm6DtG_7wThaBVNN5bX3KFfPO0Snlrda0FZlrhTuHeVR5aqTVimYY-xXsU3GzMQQXuQRHpf_zxyNlwNSPtEjR3gIIYskaYKJRoroPx8E_Px29gIB6Mz8xFRqgeHF4c' http://localhost:8080/myapp/customers/101
---Customer---
ID: 101 First: John Last: Adams
EMail: jadams@example.com
City: Braintree State: MA Birthday 1735-10-30
bash-3.2$

Now, issue the same call for the information on user ID 101 while also passing the JWT of a user that is a member of one of the admin groups (in this example, the user ‘Joe’):

curl -X GET -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiJ9.eyJzdWIiOiJqb2UiLCJpc3MiOiJva2VyYS5jb20iLCJncm91cHMiOlsiYWRtaW4iLCJjYXRfcGVyc29uIl0sImV4cCI6MTU5NDMxMjk2OH0.LAxeq5YoNrZh0vfsVjjLKK_ahHWTmkqhAb6NGAc4QDGpzeCeUUHrMp5ja3uHNv3_glzIBmb8YR-ZHA8LJ9aRccGG0xUA41Z6j1ne4sd1Wy2oSHrbg21ZgDGNezsHsdI64adcDQKGyW5AHGLMPMcXahRwIqIsX1bRw-0IFwb3-T5LG7SRgHlotURKMSW-z67aWazeuD1u5oaMdAxhU1yPXKo6MBtS9M_0a17-f3nQQjw-1dosxwzJKpYsJZeIMdq2kDN-qS65bgD1frkhASj7kxgKdaYpfqIPeNaa4ZL6pEqRZcqZvxApofbPKNbQP7aM81agsg9sPVKjcBEhl3RdNAuTHOiA6xUEqTGtmSH2zLKxE—1JIuq_La5pudR9PBIwkgY0GMw5hPZ_kfKv-o-wk2uT7w4YhryWhg-TvT_AkI_AOni8K23_r_D615njm09UJVr_sLpHVs-pLgN_KbLsc14oyGr24t-S65y8RsfwA6xDs7WE17WKeJkpW5fQNMBDlP80FhN3fRs2cnqMXzUazPmOgqjPowTeOoGJpkNpVv4PxoSyHUV9mHOiP9Laso7gv9gmulZYcPr7eqWkATHUiThWfoJL0OtWNqxW-7mXhQZQXBX9w5fTK6Y10KhGBS3VdMrWh7wBvk5wgSVeDoFFaEJz0Icv9zRk_3zmBNsZ5Q' http://localhost:8080/myapp/customers/101
---Customer---
ID: 101 First: John Last: Adams
EMail: jadams@example.com
City: Braintree State: MA Birthday 1735-10-30

SSN: 222-33-444
bash-3.2$

The returned data now includes the user’s social security number. Ignoring the temporal fallacy, this highlights a common pattern with JWTs in that it uses the JWT’s content for both authorization (proving that the call is the user ‘joe’) and authentication (the user is a member of a group that we have granted administrator privileges to).

Summary

The modified REST server code in this toy example uses JWTs for both authentication and, for the /{id} endpoint, authorization. This approach of using JWTs to protect API calls can easily be extended to other languages (see jwt.io for a list of libraries in a variety of languages) and calling patterns. It’s a simple and effective way to use well documented and tested code to protect your services.

One aspect of JWTs that we didn’t discuss is the fact that you can add any arbitrary claims that your system finds useful. In our example token, we added the “groups” claim to enable our system to do authorization based on group membership. You can imagine adding a claim like “useremail” if you wanted services to be able to send emails to the token’s owner. This extensibility is powerful and another benefit of moving to JWTs as the mechanism for your system’s authentication system.

If you are an engineer that enjoys solving practical problems with a healthy mix of engineering rigor and thoughtful design, we’re hiring in SF and Seattle: https://www.okera.com/careers/

Acknowledgements

The JWT generator code was largely written by my colleague, Swapnil Shah. The fine folks at jwt.io run a great website that we’ve made heavy use of. Postman was also quite useful for us in both our JWT journey and in the writing of this blogpost.