Hasura + Supertokens
Tags: javascript, hasura, supertokens
Published: 2022-04-01 19:03:39

Should you write your own user authentication code? As I prepare to build some new applications this question is first in my mind. I generally feel very uncomfortable with the idea of handing the most critical part of an app over to a 3rd party. What if that 3rd party is a Google or a Facebook and they decide that they need to censor you since they seem to think that they are moral authorities nowadays?  Besides that risk, here a few other pros and cons I think apply to implementing custom authentication:

Pros
- Full control
- No vendor lock-in
- Lower cost

Cons
- More code to maintain
- User records in different database
- High risk of unknown vulnerabilities

I recently heard about [Supertokens](https://supertokens.com/) from a [YouTube](https://www.youtube.com/watch?v=qXUl3VsbA6o&t=6s) video.  Because it is open source, runs on postgres, and can be self hosted I wanted to see if it could eliminate some problems associated with using 3rd party authentication.

The fastest way for me to do try it out was to refactor my [Hasura Starters](https://github.com/aaronblondeau/hasura_starters/tree/explore/supertokens/node) code to use Supertokens for auth instead of custom JWTs.

My existing codebase uses several [custom actions](https://hasura.io/docs/latest/graphql/core/actions/index/) to provide common authentication workflows:
- register
- login
- whoami
- changePassword
- destroyUser
- resetPassword

Instead of using the UI components provided Supertokens I wanted to re-write each action to interact with Supertokens instead of my custom code.  Everything turned out nicely! For example, all of my hand-made user registration code turned into this one line:

```
const signup = await EmailPassword.signUp(email, password)
```

All of my other custom actions were also easy to convert.  The only code that is slightly ugly is the process of getting the jwt to return from the login action ([based on this code](https://supertokens.com/docs/emailpassword/common-customizations/sessions/with-jwt/read-jwt)):

```
await Session.createNewSession(res, user.id)
let sessionHandles = await Session.getAllSessionHandlesForUser(user.id)
let token = ''

for (const handle of sessionHandles) {
  const sessionInfo = await Session.getSessionInformation(handle)
  const jwt = sessionInfo.accessTokenPayload["jwt"]
  if (jwt) {
    token = jwt
  }
}
```

I'm not sure what could happen here if the user has a ton of sessions.  The response object supplied to createNewSession also comes from Hasura's call to node and not the request from the user's browser.  It would be nice if there was a version of createNewSession that didn't need a response object.

Some other nice things I found about Supertokens are:
- Provides ways to customize emails (or just generate email validation and password reset tokens yourself).
- Was able to store data in same postgres instance as my app.
- Docker image was easy to use and customize.
- Project is active on GitHub.

However, I'm not 100% sure yet if I will use Supertokens in my upcoming projects.  There were a few things that I'm worried might become long-term liabilities:
- Database schema is fragmented and would be hard to use.
- Documentation can be difficult to navigate.
- It would be difficult to expire existing JWT's on password change (would have to implement a token blacklist).
- What would the switchover path be if Supertokens stops being maintained?
- How could multi-tenant roles be implemented within Hasura when users are in a separate schema (or even database).