Vladimir Dyuzhev, author of MockMotor

Vladimir Dyuzhev
MockMotor Creator

Complete JWT Auth Service on MockMotor


Let's create a JWT Auth service on top of Mockmotor. Just for the fun of it.

What is JWT?

I have quite a long blog post about JWT. Please read it.

What Do We Need from JWT Auth Service?

Our JWT service should have two operations:

Authenticate Operation

This is the first operation a client calls. This operation should:

1 Reads user credentials (id and password) from the request
2 Verifies the credentials against the DB (mock accounts)
3 Generates a time-limited token for the user

The token payload should have the user’s id and some useful information, for example, ACL, embedded.

Verify Operation

This operation checks that the token is presented and still valid:

1 Takes a token from the Authorization: Bearer header
2 Verifies the token’s integrity
3 Verifies the token’s expiration time
4 Decodes the token and gives the caller the id and ACL list

Let’s start!

Step 1. Create a Dedicated Environment

We should have a separate environment for our JWT Auth service. We’re going to use mock accounts for storing user’s credentials. While we could mix the account fields used for JWT with account fields used by other services, it is a bit messy. It is much cleaner to create a new environment.

Step 2. Create a JWT Auth Service

This is an obvious step. We’re building a service, so we create a mock service. Duh.

Once we saved the service, MockMotor gave it new permanent port numbers: 20008 (HTTP) and 20009 (HTTPS).

Step 3. Create User Accounts

Now we need to decide who can get our tokens.

We plan that the users call JWT Auth service with their credentials. For example, like this:

POST https://127.0.0.1:20009/auth HTTP/1.1

{
	"login":"john",
	"password":"password1"
}

Once the service receives this request, it should find the mock account for this login and compare the password on record with the one in the request.

Hence we need to store at least login and password values.

Then the service needs to create a JWT token, placing ACLs into its payload. So we need to store acl too.

The token must be time-limited, and the limit should be configurable per user. Let’s store ttl then.

And, finally, it’s no good if all users share the same secret for JWT. Hence we should have a secret field in the mock account.

Let’s define mock account properties:

Step 4. Create Users

Now let’s create a couple of users (mock accounts).

Let’s have user john with password password1 and secret secret1, with ttl of 10s and acl of read|write:

And let’s have user jane with password password2 and secret secret2, with ttl of 15s and acl of read|write|delete.

This is how the whole list of users looks:

Yes, the passwords are in cleartext. MockMotor is not a PROD system, but a test one. Passwords in mock accounts are not real passwords to anything valuable, so there is no point in making them obfuscated.

Step 5. Create the Authenticate Operation

Now, the most fun part.

Let’s add a response that generates a token.

What happens here?

1 The request must use HTTP POST.

2 The request must use the relative URI of /auth. For instance, https://127.0.0.1:20009/auth.

3+4 The response selects a mock account where login property matches the request login field.

5 However, if the mock account is not found, the response continues its execution. This is to generate the HTTP 401 response.

6 In the payload script, we check if the account is not found, or found but with different password, and set the payload to an empty string if that is true. That is the response body for HTTP 401.

7 We prepare the JWT header. We’re going to use the HS256 signature.

8 We calculate the expiration time as the number of seconds in the account.ttl property from now. Note we must have to use parseInt() - the account properties are strings, and JS is very weird when it adds strings to a number.

9 We prepare the payload of JWT. It contains the account login (so we can use it during the /verify call), expiration time (this is one of the standard JWT fields) and acl, which are ACLs taken from the account.

10 Now we encode the JWT token using the account-stored secret: account.secret.

11 We form the response payload containing the token, ACLs and the expiriation time. We only need to send the token back, and the rest is added just for debug.

12 HTTP status is set to 401 if the mock account is not found or its password is not matching the stored value. We could have compared the output to the empty string as well. I just copy-pasted the condition from the first line of the payload because I’m lazy. If the account is found and the passwords match, the status is 200.

13 And finally, we add some delay to look legit. Ideally, we should add a longer delay for HTTP 401 to prevent brute-force attacks, but this is a project for fun, not for trenches.

Save it and let’s try it.

Request:

POST https://127.0.0.1:20009/auth HTTP/1.1
Content-Type: application/json
Content-Length: 45
Host: 127.0.0.1:20009
Connection: Keep-Alive

{
	"login":"john",
	"password":"password1"
}

Response:

HTTP/1.1 200 OK
Date: Wed, 18 Mar 2020 03:16:32 GMT
Content-Type: application/json;charset=utf-8
X-MockMotor-Delay: 300
Transfer-Encoding: chunked

{"acl":["read","write"],"exp":1584501402958,"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2wiOlsicmVhZCIsIndyaXRlIl0sImxvZ2luIjoiam9obiIsImV4cCI6MTU4NDUwMTQwMn0.9HwAXSD-OkT1vUFbWWrU7j8uqmoSxIFZ3mTPXHDouqI"}

You can take this token to https://jwt.io for validation. Provide the secret password1 and it validates:

What if We Provide a Wrong Password?

Wrong password, unknown user or missing login field - all produce HTTP 401:

POST https://127.0.0.1:20009/auth HTTP/1.1
Content-Type: application/json
Content-Length: 39
Host: 127.0.0.1:20009
Connection: Keep-Alive

{
	"login":"vlad",
	"password":"any"
}
HTTP/1.1 401 Unauthorized
Date: Wed, 18 Mar 2020 00:05:16 GMT
Content-Type: application/json;charset=utf-8
X-MockMotor-Delay: 300

Step 6. Create the Verify Operation

Alright, now let’s add a response that verifies the token.

What happens here?

1 The request can use any HTTP method.

2 The request must use the relative URI of /verify. For instance, https://127.0.0.1:20009/verify.

3+4 The response selects a mock account where login property matches the login field from the JWT payload. We decode the payload with a null password, meaning we can’t verify the signature, but we don’t have to do it at that point, we just need to get the claimed login.

5 If a mock account with the provided login is not found, the response continues its execution. This is to generate the HTTP 401 response.

6 In the payload script, we check if the account is not found, and set the payload to FAIL message if that is true. That is the response body for HTTP 401.

7 We decode JWT with the secret from the mock account and save any errors into the error variable. Note that token expiration is also reported as an error.

8 If no decoding errors found, we have verified the token. We form the OK response.

9 Otherwise, we form a FAIL response and provide some debug information.

10 We set the status to 401 if the mock account is not found or there are any errors verifying the token. Otherwise, the status is 200.

11 And finally, we add some delay for gravity.

Testing the Verification

A successful response looks like this:

HTTP/1.1 200 OK
Date: Wed, 18 Mar 2020 03:26:19 GMT
Content-Type: application/json;charset=utf-8
X-MockMotor-Delay: 500
Transfer-Encoding: chunked

{"jwt":{"payload":{"acl":["read","write","delete"],"login":"jane","exp":1584501994},"header":{"typ":"JWT","alg":"HS256"}},"error":null,"status":"OK"}

and once the token is expired, the response changes to:

HTTP/1.1 401 Unauthorized
Date: Wed, 18 Mar 2020 03:26:39 GMT
X-MockMotor: BUILDNUMBER
Content-Type: application/json;charset=utf-8
X-MockMotor-Delay: 500
Transfer-Encoding: chunked

{"jwt":{"payload":{"acl":["read","write","delete"],"login":"jane","exp":1584501994},"header":{"typ":"JWT","alg":"HS256"},"error":"The Token has expired on Tue Mar 17 23:26:34 EDT 2020."},"error":"The Token has expired on Tue Mar 17 23:26:34 EDT 2020.","status":"FAIL"}

Let’s Test it All

I’ve made a SoapUI project that tests this service. You can download and try it. It runs against the MockMotor Demo installation.