codeburst

Bursts of code to power through your day. Web Development articles, tutorials, and news.

Follow publication

Build social media platform with the Steem Blockchain #3 — Server Side Authentication with Steem.js and JWT

Lachlan Miller
codeburst
Published in
7 min readAug 27, 2018

Image: Fintech News

This part of the series focused on authentication. In the previous article, I demonstrated how to post to the Steem blockchain by hardcoding your credentials. This time, we will create a login feature, so a user only needs to enter their initials once. This will be the first article to introduce a server, using Express for Node.js, and JWT (JSON Web Token) for authentication.

Part 1, Storybook, Vue and Steem.js is here and Part 2, posting a comment to the blockchain is here. The source code for this part is here.

Check out my Vue.js 3 course! We cover the Composition API, TypeScript, Unit Testing, Vuex and Vue Router.

Install the Server Dependencies

First, install express, the server framework we will be using, along with jsonwebtoken and body-parser by running yarn add express jsonwebtoken body-parser --save. Next, create a server directory inside src, and inside of server a index.js. This is where the server will run from. I also recommend installing nodemon, which automatically restarts the server after each change - otherwise you would have to manually kill and restart it. You can install nodemon globally by running yarn global add nodemon.

Setting up the Express Backend

Before doing any work on the front end, let’s set up JWT authentication. This is not an Node.js/Express tutorial, but it’s fairly easy either way. If you need to, check some basic guides about Express first. I will, however, explain JWT authentication as we go.

I will be using curl, a command line tool that is available on Mac OS and Unix systems, but not Windows by default. You can install it for Windows, the site is here and the several guides are here.

In src/server/index.js, add the following minimal Express application:

We create a single /api/login endpoint, which current returns 403 (forbidden status code) no matter what. Let's test it out, to make sure it's working. Run nodemon src/server/index.js in one terminal, and in another use the follow curl command:

If everything went well, curl should have returned Forbidden. If not, check everything is typed correctly.

Validating the User on the Server

Let’s talk about JWT for a second. Basically, the client will attempt to log in using some credentials, in our case a Steem username and password. Once we authenticate the user, using Steem.js, the server will generated a “JSON Web Token” using jsonwebtoken, which we installed earlier. We then send the token back to the client. On all subsequent requests, the client will include the token. If the token is successfully validated by the jsonwebtoken, the request is allowed. Our workflow wiil be something like this:

  1. Client sends username/password to the server’s api/login endpoint.
  2. The server validates the login using Steem.js.
  3. If the credentials are correct, create a token and send it back, with any extra information.
  4. Now the client is “logged in”. The client should resend the token each request to prove is it the authenticated user, and not a hacker/someone impersonating the real user’s identity.

Let’s update the login endpoint to achieve the second step above. There is a lot of new code - the explanation follows.

There is a lot going on here. First, we extract the username and password from the request body. Next we use the getAccounts method (documentation here, which returns a bunch of public information about the account. We are interested in the posting public key. You can see the previous article for more details on what other fields are in this response.

Next, we get the private keys based on the username and password combination. Note that even if the username or password is wrong, the getPrivateKeys method still returns as public/private key combination - the will just be incorrect. Now we validate the two keys using wifIsValid (documentation here). If the keys are a match, we return the original username - otherwise, the original 403 forbidden. Note we still have not touched on the JSON Web Token - that will come next, once we make sure this is working.

Test it out using curl like this:

Try your real password — you should get your username in the response. Next, change your password, and you should get the 403 forbidden status.

Implementing JWT

Now we can verify a user, the next step is to generate the JWT. Generating the token is extremely simple. Inside of if (isValid), add the following:

The documentation for jwt.sign is here. The first argument can be any payload - it is common to use a username or id. The second argument is a secret key - this is something unique to your application, that you should not share. For this tutorial, I generated a random 16 digit hex number. You should never check this into source control or share it with anyone, or your server's security can be compromised.

Finally, the token is returned to the user. All subsequent requests (such as posting) will require this token to be sent from the client. Once the client has this token, however, they will not need to log in again until the token expires. You can set the expiry date in the third argument, an options object. This is in the documentation linked above.

Try running the curl command again now. If you supply a correct username/password combination, you should get a response like this:

This token is used for authentication. To see it in action, create another endpoint, /api/posts:

The match method for json.sign is jwt.verify. Using the token created earlier, and the secret key, it can determine if the token was originally received when the user successfully authenticated, or if it is a malicious party trying to compromise the application.

You may have noticed we refer to req.token - this will be sent by the client. Also, we add a verifyToken middleware. Basically, before executing the jwt.verify logic, we will call verifyToken. This method will check if the client correctly included the JWT. It looks like this:

The JWT has to be included in a specific format. The format is Authorization: bearer [token]. To get the token from the request, we first access the bearer [token] part using req.headers['authorization']. If it exists, we get the token part using split, and add it to the request. The request will then be passed on to the app.post('/api/posts') method. If the token is not present, we just return 403 Forbidden.

To see if it works, run a new curl request that looks like this:

We are posting to the /api/posts endpoint, with the correct token. If you did everything correctly, the response should look something like {"auth":{"username":"xenetics123","iat":1535377462}}. Notice we did NOT include our username or password, just the token - which requires the username and password to be created in the first place. This is just for a test - in the next part of the series, we will implement the logic to post to the Steem blockchain at /api/posts, and return a status to reflect the post was successful.

Writing Tests for the Server Code

Now JWT authentication is working, let’s finish up with some unit tests. First, a small tweak to src/server/index.js is needed to let us run the server on a different port to the main application, and easily restart it between tests. Update the part of src/server/index.js where the server is created:

This way, the server will only run if there is no module.parent - basically, when it is not being required in another file.

Create a directory called server in tests/unit/specs/server and add a index.spec.js file. Before writing the actual tests, we have some housekeeping to do.

Mocking steem.js

Testing is not the main purpose of this series, so I will not provide an in depth explanation of how exactly the tests are working. Trying things out yourself is the best way to learn. Either way, tests are part of any development workflow, so I’m going to go over them quickly anyway.

We know steem.js and the functions it provides are working correctly — it has its own set of unit tests, written by the developers of the library. We want to test the our own code. That means we mock the steem.js api — getPrivateKeys, wifIsValid and so forth. Jest allows us to mock a node module like so:

We declared a global mockWifIsValid variable. This lets us simulate a correct or incorrect username/password combination by saying mockWifIsValid = true or mockWifIsValid = false. Now the actual tests - first one for the case of incorrect credentials:

Kind of long, but it’s worth it to be confident the application is working correctly as we develop. beforeEach test, we start the server, and afterEach we close it. Then to simulate incorrect credentials, mockWifIsValid is set to false. After making the request using Node's http module, we expect(res.statusCode).toBe(403).

Next, the passing case:

We don’t need to verify what the token is. As long as the token property is present in the response, I'm confident that the jsonwebtoken module is working correctly.

This test can be run individually using yarn jest --runTestsByPath test/unit/spec/server/index.test.js. If you did everything correctly, they should pass. Run the entire suite using yarn unit.

Conclusion

This was a pretty long article with a long of information. It covered:

  • what JWT is and how it works
  • setting up Express and working with Node.js
  • how to use getAccountsAsync to get account details
  • using getPrivateKeys to get keys using a username/password
  • verifying the validity of the keys using wifIsValid
  • writing tests using Jest and jest.mock

The next part will focus on integrating the Vue frontend from the previous article, and the server we created here, to allow a user to log in, then post without retyping their password by using JWT authentication.

The source code for this part is here.

Published in codeburst

Bursts of code to power through your day. Web Development articles, tutorials, and news.

Written by Lachlan Miller

I write about frontend, Vue.js, and TDD on https://vuejs-course.com. You can reach me on @lmiller1990 on Github and @Lachlan19900 on Twitter.

No responses yet

Write a response