Fast Serverless Authentication with Next.js and PropelAuth
Tags: nextjs, javascript, webdev, auth
Published: 2021-11-29 23:26:07

[Next.js](https://nextjs.org/) is a [React](https://reactjs.org/) framework that provides a lot of useful features out of the box. One of these powerful features is [API routes](https://nextjs.org/docs/api-routes/introduction), which allows you to make an API/backend entirely within your Next.js application.

```js
// pages/api/hello.js
//   requests to /api/hello return {"message":"Hello, World!"}
export default function handler(req, res) {
  res.status(200).json({ message: 'Hello, World' })
}
```

Services like [Vercel](https://vercel.com) and [Netlify](https://www.netlify.com/) manage your API routes in a serverless environment, meaning you get a very scalable API and your code can be deployed globally. 

In this post, we'll create an authenticated API route that looks like this:

```js
// pages/api/whoami.js
export default async function handler(req, res) {
    // check if the request contains a valid token
    await requireUser(req, res)
    
    // req.user is automatically set by requireUser 
    res.status(200).json({user_id: req.user.userId}) 
}
```

and make a React component that sends authenticated requests to this API route.

## Making an unauthenticated request

Before we dive into *authenticated* requests, let's first make an endpoint that we request on the frontend. If you don't already have a Next.js project, you can create one with:

```shell
$ npx create-next-app@latest
```

We will use the library [SWR](https://swr.vercel.app/) to fetch data, but you can also use `fetch` directly or a different library like `axios`. 

```shell
$ yarn add swr # or npm i --save swr
```

### Making an API route

Any files in `pages/api` are treated as a route. Let's make a new file `pages/api/whoami.js`:

```js
export default function handler(req, res) {
  res.status(200).json({ user_id: 'Not sure yet' })
}
```

And that's all, we can test this route with curl

```shell
$ curl localhost:3000/api/whoami
{"user_id":"Not sure yet"}
```

### Making a component that fetches 

We have the backend, now we just need the frontend. Make a new file `components/WhoAmI.js`:

```jsx
import useSWR from 'swr'

// Used by SWR to fetch data
const fetcher = (url) => fetch(url).then(res => res.json())

const WhoAmI = () => {
    const { data, error } = useSWR('/api/whoami', fetcher)

    if (error) return <div>failed to load</div>
    if (!data) return <div>loading...</div>
    return <pre>{JSON.stringify(data)}</pre>
}

export default WhoAmI
```

SWR makes everything pretty simple. All we need to do is add this component to a page. Any files in `pages` are automatically treated as frontend routes (excluding `pages/api` which are API routes), so we can make a new file `pages/whoami.js` which is automatically rendered when we visit `localhost:3000/whoami`.

```jsx
import WhoAmI from "../components/WhoAmI";
import Head from "next/head";

export default function WhoAmIPage() {
    return <div>
        <Head><title>Who Am I?</title></Head>
        <WhoAmI/>
    </div>
}
```

If you go to `localhost:3000/whoami`, you will now see a brief flash of `loading...` followed by `{"user_id":"Not sure yet"}`.

## Adding Authentication

We wrote [an introduction to user authentication](https://blog.propelauth.com/understanding-user-authentication-from-scratch/) to provide more context, but as a quick summary - we need some way to register new users and some way for our API routes to know which user (if any) created a request.

### Setting up PropelAuth

We will use [PropelAuth](https://www.propelauth.com) for both of these. PropelAuth provides a hosted, configurable UI which manages all aspects of authentication for us, from login/signup to account pages and transactional emails. For B2B/SaaS use cases, we also get support for creating organizations and allowing our users to invite other users to their orgs.

After signing up, we can configure the look and feel of all our auth pages/emails:

![A user configuring hosted auth pages](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/od3sbodqpwadatbaly3j.gif)

We can also optionally enable features like social logins or conversion tracking.

### Integrating our Frontend

Since PropelAuth handles logging users in, how do we know that a user is logged in our Next.js app? We need to use `@propelauth/react`. The full documentation is available [here](https://docs.propelauth.com/getting-started/frontend-integration/nextjs.html). First, we install the library:

```shell
$ yarn add @propelauth/react
# OR
$ npm install --save @propelauth/react
```

Then, in `pages/_app.js`, we wrap our application with an `AuthProvider`. The `AuthProvider` reaches out to our PropelAuth instance and fetches our current user's metadata, if they are logged in. You'll need your `authUrl` which you can find in your dashboard under **Frontend Integration**.

```jsx
import {AuthProvider} from "@propelauth/react";

function MyApp({Component, pageProps}) {
    return <AuthProvider authUrl="REPLACE_ME">
        <Component {...pageProps} />
    </AuthProvider>
}
```

Now, we are ready to update our component. Let's look at the changed code first and then break it down:

```jsx
import {withAuthInfo} from "@propelauth/react";

// 1. fetcher now takes in an accessToken and passes it in an Authorization header
const fetcher = (url, accessToken) => fetch(url, {
    method: "GET",
    headers: {"Authorization": `Bearer ${accessToken}`}
}).then(res => res.json())

// 2. function is wrapped with withAuthInfo
const WhoAmI = withAuthInfo((props) => {
    // 3. props.accessToken comes from withAuthInfo
    const { data, error } = useSWR(['/api/whoami', props.accessToken], fetcher)
// ... nothing changed after this
```

We made three changes. The first change is that we pass in an `accessToken` to our fetcher, and our fetcher passes it along in the header of the request. What is an `accessToken`? You can read more about it [here](https://docs.propelauth.com/reference/other/access-token.html), but at a high level, PropelAuth creates `accessToken`s for your users. It then provides metadata that your backend can use to verify these `accessToken`s. Passing it along in the `Authorization` header is a standard convention, and our backend will expect this exact format (`Bearer TOKEN`).

The second change is we called `withAuthInfo` with our React component. `withAuthInfo` automatically injects useful props into our Component, which is where `props.accessToken` comes from. We could also use other properties like `props.isLoggedIn` or `props.user.email`.

The third change is we need to pass our access token into useSWR for our fetcher to use.

### Integrating our Backend

Our frontend is done, now we just need to update our backend to verify the `accessToken`s that are passed in. To do this, we'll use `@propelauth/express`, since Next.js API routes support Express/Connect middleware.

```shell
$ yarn add @propelauth/express
# OR
$ npm install --save @propelauth/express
```

Then, we'll create a new file `lib/propelauth.js`

```js
import {initAuth} from "@propelauth/express";

const propelauth = initAuth({
    authUrl: "REPLACE_ME",
    apiKey: "REPLACE_ME",
    manualTokenVerificationMetadata: {
        verifierKey: "REPLACE_ME",
        issuer: "REPLACE_ME"
    }
})

export default propelauth
```

Your specific values can be found in the **Backend Integration** section of your PropelAuth project. This exports a set of functions like `propelauth.requireUser`, which will make sure a valid `accessToken` was provided and automatically set `req.user` with the user's information. The full reference is available [here](https://docs.propelauth.com/reference/backend-apis/nextjs.html).

The Next.js docs also provide a `runMiddleware` function, which we need both for our auth middleware and any other middleware (like CORS middleware). We can place this in `lib/middleware.js`:

```js
// From the Next.js docs about running middleware:
//   Helper method to wait for a middleware to execute before continuing
//   And to throw an error when an error happens in a middleware
export default function runMiddleware(req, res, fn) {
    return new Promise((resolve, reject) => {
        fn(req, res, (result) => {
            if (result instanceof Error) {
                return reject(result)
            }

            return resolve(result)
        })
    })
}
```

And now we have everything we need to update our `pages/api/whoami.js` route:

```js
import propelauth from "../../lib/propelauth"
import runMiddleware from "../../lib/middleware"

// Calls our runMiddleware function with PropelAuth's requireUser function
const requireUser = (req, res) =>
    runMiddleware(req, res, propelauth.requireUser)

export default async function handler(req, res) {
    // Verifies that a valid accessToken is provided
    await requireUser(req, res);
    // req.user comes from requireUser
    res.status(200).json({ user_id: req.user.userId })
}
```

Note that `requireUser` does NOT need to make any external requests to validate the token. This allows our authentication step to be fast regardless of where the function is currently running.

### Testing

We now have everything we need. If we are not logged in, and we visit `localhost:3000/whoami`, we will get a `401 Unauthorized` error. If we sign up and visit `localhost:3000/whoami`, we will see:

```json
{"user_id":"5395219c-7d05-4085-a05c-5f5e98ede166"}
```

Speaking of logging in and out, if we want to make that easier from within our app, we can use an example from the docs to add `Signup`/`Login` buttons if we aren't logged in, and a `Logout` button if we are.

```jsx
import {withAuthInfo, useLogoutFunction, useRedirectFunctions} from '@propelauth/react';

function AuthenticationButtons(props) {
    const logoutFn = useLogoutFunction()
    const {redirectToSignupPage, redirectToLoginPage} = useRedirectFunctions()

    if (props.isLoggedIn) {
        return <button onClick={() => logoutFn()}>Logout</button>
    } else {
        return <div>
            <button onClick={redirectToSignupPage}>Signup</button>
            <button onClick={redirectToLoginPage}>Login</button>
        </div>
    }
}

export default withAuthInfo(AuthenticationButtons);
```

In this case, we are using the React hooks `useLogoutFunction` and `useRedirectFunctions` to log the user out or navigate them to the signup/login pages. We are also using `withAuthInfo`, but this time instead of getting the `accessToken`, we just need to check if the user `isLoggedIn` or not.

## Summary

In this post, we were able to build a decently complex application very quickly. With PropelAuth, our users can register, login, manage their account information, etc. Afterwards, we used Next.js on the frontend make an authenticated request to our backend. We also conditionally rendered different buttons depending on if the user was logged in or not.

Our backend is also powered by Next.js and can scale easily with API routes. These serverless functions can be hosted anywhere, but will always be able to quickly determine which user made an authenticated request.

## Bonus: B2B/SaaS

While we mostly covered B2C authentication, where each user is independent. You can also use PropelAuth for B2B authentication, where each user can be a member of an organization. PropelAuth provides you with UIs for your end-users to manage their own organizations and invite new users. PropelAuth also provides a role-based system where users within an organization can be Owners, Admins, or Members.

On the frontend, we can still use `withAuthInfo`, but now we can use an `orgHelper` to help us manage a user's organizations. For example, here's a React component that displays a list of organizations that the current user is a member of:

```jsx
const Orgs = withAuthInfo((props) => {
    // get all orgs that the current user is a member of
    const orgs = props.orgHelper.getOrgs();
   
    // Display their names in a list
    return <ul>
        {orgs.map((org, i) => 
            <li key={i}>{org.orgName}</li>
        )}
    </ul>
})
```

We can also use functions like `orgHelper.selectOrg(orgId)` and `orgHelper.getSelectedOrg()` to select/get a single organization so our frontend can operate only on one organization at a time.

On the backend, we can use the middleware `propelauth.requireOrgMember` similarly to how we used `propelauth.requireUser`, except this will verify that the user is also a member of an organization.

If you have any questions, please reach out at support@propelauth.com