Next.js is a React framework that provides a lot of useful features out of the box. One of these powerful features is API routes, which allows you to make an API/backend entirely within your Next.js application.
// 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 and Netlify 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:
// 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:
$ npx create-next-app@latest
We will use the library SWR to fetch data, but you can also use fetch
directly or a different library like axios
.
$ 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
:
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
$ 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
:
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
.
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 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 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:
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. First, we install the library:
$ 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.
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:
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, 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.
$ yarn add @propelauth/express
# OR
$ npm install --save @propelauth/express
Then, we'll create a new file lib/propelauth.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.
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
:
// 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:
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:
{"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.
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:
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
Top comments (1)
Don't you have a permanent free plan?