Validate jwt tokens using Clerk, HapiJs and Expo
I was working on a project that is using clerk for its authentication layer and it really looks great, but this particular project was built with hapi on the backend and expo for building the frontend so today I’m going to share a pretty basic way to validate sessions using the clerk sdk with hapi.
TL;DR
Here’s the repo with the full code in case you don’t want to spend time reading me.
Creating the expo app
There’s a Get started with expo page on the clerk’s docs, but to be honest it was kinda confusing and I got a couple of errors so I’m going to describe my personal experience on how I did all the steps.
The easy way to create an expo app is using the create-expo-app package
1 | npx create-expo-app myapp |
You can replace myapp with your application’s name.
Installing clerk-expo
In order to install @clerk/clerk-expo we need to install react-dom. Yeah weird, right? But this library has it as a peerDependency. Maybe kinda makes sense because you can use expo to build web apps as well using react-native-web and I’m asumming react-dom is a dependency there? But this is actually my first experience with clerk so I don’t really know the reason.
1 | npm i react-dom |
Ok now you can install the clerk-expo library without any problems
1 | npm install @clerk/clerk-expo |
Adding the ClerkProvider
Ok now we can start writing code, first let’s wrap our whole app with the ClerkProvider component. This provider needs two things: the first one is the public key. You can get it from your dashboard - select your app - look for the Api Keys option in the left menu and then copy the Publishable key.
1 | import React from "react"; |
Great, but remember we needed two things for the clerk provider? The second thing is a prop called tokenCache that is used by clerk to save the jwt client once our users are logged in. For this let’s just follow the recomendation of the clerk team and use the securestore library.
1 | npx expo install expo-secure-store |
And in the code it looks like this:
1 | // the rest of the code is omitted for brevity |
Starting the app
Let’s start our app first using the expo cli:
1 | npx expo start |
Creating our Login Form
I’m not going to focus on the UI/UX since that’s way out of the scope of the post, so yeah it’s going to look awful. Our app is composed of basically three actions: Login, fetch user orders (to the hapi API) and logout.
Let’s create a new component called OrdersApp and inside of it we’re going to implement all of the different actions we need starting with our Login Form:
1 | import { View, Text, Pressable, TextInput } from "react-native"; |
Nothing crazy here two TextViews to handle the username and password and the button where we have the part we care about: the login process with clerk. Let’s complete the logic inside the onLogin function. The first thing to do is importing the useSignIn hook from clerk-expo.
1 | import { ClerkProvider, useSignIn } from "@clerk/clerk-expo"; |
Now let’s use our new hook and extract two methods: signIn and setSession.
1 | function OrdersApp() { |
and inside of the onLogin function we need to verify the credentials of the user using the create method from the signIn object. We pass an object with the attributes identifier and password. For this example the idenfitier is a valid email address.
1 | async function onLogin() { |
If the create process goes well clerk is giving us an identifier to create a session in the createdSessionId attribute and we’re going to use it along the setSession method.
1 | async function onLogin() { |
And let’s include this component in our main App component.
1 | export default function App() { |
Our form looks like this:
Showing user information after login is successful
The session is where we can get the information about our current user so let’s show some user information. But you probably have noticed that we just called the setSession function but we don’t save the result in a variable or state and that’s on purpose because clerk already give us a hook to access the session information and that’s the one we’re going to import now useSession.
1 | import { ClerkProvider, useSignIn, useSession } from "@clerk/clerk-expo"; |
and this hook give us the session object:
1 | function OrdersApp() { |
ok now we can make a simple validation: if the session is defined we can show the information of the current user, otherwise we go back to our login form because that means nobody is logged in.
1 | function OrdersApp() { |
Ok but we really don’t have any credentials to test right now, so we need to open the clerk dahsboard select the app we’re working on and then go to the Users option in the left menu and then click on the Create button.
This is going to open a modal where we can enter an email and a password. After that we’re ready to test our fresh new user.
If we enter the email and password we’re going to see a screen with the email of the user.
Great, we already have a login process working perfectly. But of course our user now is trapped inside this screen.
Adding our Logout process
Let’s fix that by adding a logout button when a session is already present:
1 | function OrdersApp() { |
pretty similar to our login process clerk give us a hook to perform a sign out of our current user, so we need to import that first: useAuth
1 | import { |
and now inside our OrdersApp component we can get the signOut method and simply call it in our onLogOut function.
1 | function OrdersApp() { |
and just like that if we press the button we’re going to see the Login Form again because the session object from the useSession hook is going to be empty. Aren’t these react hooks a beautiful thing, are they?
And the last thing we need to build for our UI is an action that makes the http request and fetch all the orders. This request is going to hit our hapi server that we’re going to create later in this post.
1 | function OrdersApp() { |
basically we’re copying the logout button and changing its text and the function for the onPress prop. Now let’s write the code to make the http request using the fetch api:
1 | function OrdersApp() { |
First we need the jwt token so the server can verify our session and clerk give us the getToken method in the session object. This method return a string that represents the jwt token so the rest of the code is a pretty simple http request with fetch and we’re including an authorization header with the jwt token we just got from clerk. Once the server has validated everything we can save the response in the state of our component using the setOrders method. We expect an array here so let’s finish this part by iterating all the orders and show them in the UI.
1 | function OrdersApp() { |
Ok we have everything done in the UI, of course our fetch api right now is not going to work because we don’t have our server yet.
Creating a HapiJs server
We can start working on the server side now, we’re going to have a /orders endpoint where we are going to validate the jwt that clerk has generated in our expo app and if the token is valid our endpoint will return a list of orders for the current user.
I’m going to create another folder called server just to keep things clear and create a new npm project
1 | # inside server folder |
now we need to install our dependencies: hapi and the clerk sdk for nodejs.
1 | npm i @hapi/hapi @clerk/clerk-sdk-node |
let’s add the code necessary to have our hapi server
1 | // server/server.js |
it looks like a lot of code, but it’s just the basic code we need to start a hapi server. Now let’s include our /orders endpoint.
1 | // code omitted for brevity |
from here if we can test our server
1 | node server.js |
if we do a curl to our server we’re going to get a 404 because we don’t have anything defined for our main endpoint.
1 | curl http://localhost:3000 |
Validating a clerk token in HapiJs
but we haven’t used the clerk sdk yet, so let’s change that by calling the library:
1 | const Hapi = require("@hapi/hapi"); |
now we need to setup our api key, you can find it in the dashboard in the API KEYS option in the Secret keys section, click on Reveal Key then copy the value and use it in the code.
1 | const Hapi = require("@hapi/hapi"); |
Great now let’s actually validate the session directly in our /orders endpoint. Remember how easy was to do things like signin, singout on the front end side because of all the helpers clerk already give us? Well for the server side is kinda the same story because there’s a method called verifySession.
And we need two params for this method: the session id and the jwt token. From our expo app we are sending the jwt token in the authorization header so let’s begin by getting that value;
1 | server.route({ |
in hapi you can get the authorization header from the headers object inside the request. After that we’re just removing the Bearer word along the empty space and storing that in a clerkToken variable. With this we already have one of the params needed for the verifySession we talked about.
Now we need the session id and according the docs we can get this value from decoding our clerkToken variable. This can be done easily because the clerk sdk already contains a decodeJwt method.
1 | // code omitted for brevity |
the decodeJwt returns 3 parts, but now we only need the payload object since here’s where we can find the sid attribute that has the session id and now we have the 2 required params for our verifySession method.
1 | // code omitted for brevity |
pretty easy to understand we call the method and since this is a promise we use the await keyword and if the promise was fulfilled we can start doing our main task that in this case is returning a list of orders. One last thing to do is dealing with the error case, since the verifySession method is a promise let’s add a try/catch around the whole thing.
1 | // code omitted for brevity |
just a try/catch around our function and using the h variable to build a response object with a 401 error which is the standard http status code for this situation.
we’re done, just remember that in order to test this api from your expo app you need to start the server
1 | node server.js |
Trying all the flow
Now we have everything to try our app: login, fetch data from an external api that validates our clerk session and the logout process.
Final Thoughts
- Clerk is going to give every user an ID and you can save that in your database in order to associate a clerk user with your application’s info.
1 | // code omitted for brevity |
For a better server side validation with hapi you can use the hapi-auth-jwt2. This is going to create a hapi strategy so you can validate ALL your routes without repeting the same code for each route. Also this library validates if the token is a jwt valid token (header, payload, signature), check the expiration attribute, etc.
If you’re not using another backend technology take a look to the API & SDK reference in the docs and use the official one in case exists.
If you have problems testing the integration with the hapi server try using your current local IP instead localhost
1 | const server = Hapi.server({ |
and make the same adjust in your fetch request
1 | const token = await session.getToken(); |
- You should ALWAYS handle errors for your promises so in a real application take that in mind both in the frontend and backend side.