The importance of setting up authentication In Next.js Considerations When Setting Up Authentication in Next.js (r)

Jan 5, 2024
Authentication in Next.js

-sidebar-toc>

The last couple of years, adding authentication your application has changed from being a complex and nebulous idea into something that is easy to simply use an API to do.

There's no shortage of example repository and tutorials that show how to use specific authentication methods in Next.js, but less about the why of what strategies, tools and tradeoffs are chosen.

This post will walk through how to approach the authentication process in Next.js beginning with selecting an authentication provider to designing ways to sign-in and deciding between server side vs. client side.

Choosing an Authentication Method or the Provider

There are a myriad of options to incorporate authentication in your app. Instead of discussing specific service providers (the topic for a separate blog post) Let's take a examine various types of solutions for authentication and a few illustrations of every. In terms of implementation, next-auth is rapidly becoming the most popular choice for integrating your Next.js application with multiple authentication providers, enabling SSO and more.

Traditional Database

This one is as simple as it gets: you keep passwords and usernames in the database known as a relational. When a user signs up to the site for the first time you create an additional row in the `users` table by providing the required information. After they sign in, you validate their credentials against what you have saved on the table. When a user wants to modify their password, you update the value in the table.

The traditional database authentication is definitely the most widely used auth mechanism in all the available applications and it's been around for a long time. It's extremely versatile, affordable and does not lock users to a specific vendor. You'll need to build it yourself, and particularly, you need to be concerned about security and ensuring those sweet, yummy passwords don't get into the inappropriate hands.

Authentication Solutions From Your Database Provider

Since the beginning of time (and thank you to Firebase, more than just a couple of years ago), it has come to be a common practice for managed database providers to offer some sort of managed authentication solution. Firebase, Supabase and AWS each offer a managed databases and managed authentication as a service via a suite of APIs that easily abstracts the creation of users and manage sessions (more on this later).

The process of logging in users using Supabase authentication is straightforward as:

async function signInWithEmail() 
 const  data, error  = await supabase.auth.signInWithPassword(
 email: '[email protected]',
 password: 'example-password',
 )
 

Authentication Solutions Not From Your Database Provider

A lot more widespread than authentication as a service from your DBaaS is authentication as a service as an entire company or product. Auth0 has been around since 2013, (now controlled by Okta) in recent years, and additions like Stytch are focusing on user experiences and generated some interest.

The UI you get as part of using Auth0 for authentication
Auth0 for authentication

Single Sign On

SSO can allow you to "outsource" your credentials to an external provider, it could be anything from an enterprise, security-focused company such as Okta or Okta to a that is more popular, such as Google or GitHub. Google SSO is ubiquitous in the SaaS world, and some developer-focused tools are limited to authenticate through GitHub.

Whatever provider(s) you pick, SSO is generally an alternative to other forms of authentication and comes with specific unique idiosyncrasies regarding integration with other platforms (be beware: SAML uses XML).

Okay, So Which One Is Best for You?

There's there's no "correct" option here, but what's right for your project will depend on the needs of your project. If you want to get things moving fast without a ton of upfront configuration, outsourcing auth makes sense (even outsourcing it completely, UI included, to an entity like Auth0). If you anticipate the need for a more intricate configuration, creating your own authentication backend is sense. If you plan to support larger customers, you'll need to incorporate SSO at some point.

Next.js is so well-liked to the point where most of these authentication providers offer Next.js particular documentation and integration guides.

   Designing Routes for Sign-Up and Sign-In, and Tips for Going to Extra Authentication Mile  

Some authentication providers like Auth0 offer complete hosted web pages for sign-up and sign-in. However, if you're developing them completely from scratch, it beneficial to build these early on during the process because they'll be required to serve as redirects when you actually begin to implement your authentication.

It makes sense therefore to design the structure of the pages, and then to integrate the requests into the backend following the fact. One of the easiest ways for implementing the auth protocol is to create two routes to use:

  • One for signing up
  • A different method for sign-in once the user already has an account.

Beyond that there's a need for edge cases like when a user forgets their password. Some organizations prefer to keep the password reset process on a separate route, while others include the dynamic UI elements on the normal registration page.

A well-designed sign-up form may not be the biggest difference between success and failing, but even small details will make an impression, and ultimately provide better UX. Here are a few collated from sites around the web which have put some additional effort into their verification methods.

1. Change Your Navigation Bar If You're in an Active Session

Stripe's call to action on their navigation bar changes depending on whether you have authenticated sessions or not. The marketing website appears like when you're not authenticated. Note the call to action for signing in.

Stripe homepage
Stripe's homepage will change the CTA depending on if you're authenticated

This is what it will look like if you are authenticated. Be aware that the call to action changes to redirect the user to their account instead of sign-in:

Stripe homepage changes
The Stripe homepage has been updated

It doesn't significantly alter my Stripe experience It's just nice.

An interesting technical aside: there's a good reason that most companies do not have the navigation bar on their marketing site "depend" on authentication. this would require an additional API request to verify an authenticated status on each webpage load, the vast majority of which is for people who may not be.

2. Include some helpful content along with the Sign-Up Form

In the last couple of years, particularly in SaaS businesses, they have begun making content available on their sign-up page to "encourage" the user to complete the sign-up. This could improve your performance on your page or at the very least, incrementally.

The page for signing up is created by Retool and includes an animated video and some logos at the bottom:

Retool signup page
If you plan to attempt this, make sure you test and ensure that the fonts on each side are the same.

Also, we do it for our signup page:

My sign in
Sign-up page

The little extra content can help remind the user what they're signing up for and why they need it.

3. If you are using a password: Suggest or Enforce a Strong One

I feel comfortable saying that it's a common belief among designers that passwords are insecure, but it's not widespread knowledge for everyone who are signing in for your products. Inviting your customers to use safe passwords is beneficial for them and also good for them.

Coinbase is pretty strict with signup and requires you to make use of a password that is secure and more complicated than just your first name:

Coinbase create account
A weak password on Coinbase

When I created one using my password manager I was good to go:

Coinbase password strength
Secure password for Coinbase

The UI did not explain to me the reason the password was not secure enough, though, or any other requirements that go beyond the number. Include them in your product copy will help ease the burden for the user, and will help keep them from having to retry their passwords.

4. Mark Your inputs to ensure They Play Nice With a Password Administrator

One-third of Americans utilize a password manager like 1Password, and yet so some forms available on the internet continue to ignore the `type==' HTML inputs. Let your forms play ball with password managers:

  • Enclose your input elements in an element of a form
  • Type in inputs and assign the label
  • Add autocomplete capabilities to your inputs
  • Don't add fields dynamically (I'm thinking of you, Delta)

It could be the difference between an instant seamless sign-in, or a gruelling manual process particularly for mobile devices.

   Choosing Between Sessions and JWT  

After your user has authenticated, you need to choose the best method to maintain the state during future requests. HTTP is stateless, and it isn't necessary to request their password every request. The are two methods of handling this that are referred to as session (or cookies) as well as JWTs (JSON web tokens) They differ in the sense of which server or the client does the work.

Sessions, AKA Cookies

When using session-based authentication, the work and logic for keeping authentication in place is managed by the server. The basic process is:

  1. User authentication is done via sign-in page.
  2. The server makes a record that represents this particular session of browsing "session." The record typically gets inserted into the database along using a random identifier as well as information about the session including when it was started, and when it expires.
  3. That random identifier - something like `6982e583b1874abf9078e1d1dd5442f1` - gets sent to your browser and stored as a cookie.
  4. Following requests made by clients, the client's identifier is included in the request and verified against the sessions table within the database.

It's fairly simple and easily tweakable with regards to duration of sessions, when they should be cancelled, and so on. However, the downside is that it can be slow on the scale of all the writes and reads to the DB however, that may not be an issue for the majority of readers.

JSON Web Tokens (JWT)

Instead of handling authentication in any subsequent requests made to servers, JWTs let you handle them (mostly) via the server side. This is how they work:

  1. User authentication is done via sign-in page.
  2. The server produces a JWT that contains information about the person who is the user, their permissions they're granted, as well as their expiration date (among many other possible things).
  3. The server recognizes that token digitally encodes the contents of it and then transmits the entire thing to the user.
  4. For each request for each request, the client is able to unlock the request token and verify that they have authority to request (all without having to connect with servers).

With all of the work that is done after the initial authentication being transferred to the client, your program can load and work significantly faster. However, there's a major problem: there's not a way to deactivate a JWT by the server. If the user is looking to exit an account or change the terms of their access is changed it is necessary to wait until you can verify that the JWT expires.

   Choosing Between the Server Side and Client Side Auth  

One of the things that it makes Next.js amazing is its built-in static rendering - if your website is static, i.e. it doesn't require other API call, Next.js automatically caches it and is able to serve it quick via the CDN. Pre-Next.js 13 determines the static nature of a web page in the absence of functions like getServerSideProps, getInitial within the file. Likewise, the versions after Next.js 13 utilize React Server Components for this purpose instead.

In order to authenticate your users, you have a alternative of rendering the static "loading" page and doing the fetching server side or doing everything server side. For pages that require authentication (1) it is possible to render an unstructured "skeleton" in order to make authentication requests to the server side. This could mean that it loads quicker, even if the initial page isn't ready to go.

Here's a simplified example from the documents that displays the loading state for as long as the user object isn't ready:

import useUser from '../lib/useUser'
 
 const Profile = () => 
 
 export default Profile

You'll need to create a loading UI here to hold space while the client makes requests post-load.

If you want to simplify processes and use authentication servers on the server side, add your authentication request into the function called getServerSideProps and Next will hold off rendering pages until your request is complete. Instead of using the conditional logic in the snippet above, you'd run something more simple, such as this streamlined Version of Next docs:

import withSession to '../lib/session load withSession into session from the library.export const GetServerSideProps = withSession(async function (Req, Res) ()
 
 const Profile = (user) => //// Show the user. No loading state is required
 return (
 
 Your Account
 Username: JSON.stringify(user.username,null)
 Email: JSON.stringify(user.email,null)
 Address: JSON.stringify(user.address,null)
 
 )
 
 
 export default Profile

There's a bit of logic to deal with authentication failing however it will redirect you to login instead of rendering an initial state.

Summary

So which one of these is right for your project? Consider assessing the confidence you have in the performance of the authentication system. If your requests are taking less than a second, you can run them server side and avoid the state of loading. If you're looking to focus on rendering something immediately and waiting for the request to come through, bypass getServerSideProps, and instead perform authentication in another location.

1 If you are using Next This is a good reason not to blanket-require authentication for each web page. It's easier to accomplish this however, it also means you'll miss some of the benefits of the web framework in the first place.

Justin Gage

Justin is an engineer writer, and the author of the well-known Techly Newsletter. He earned his B.S. on Data Science before a stint in full-stack engineering and now is focused on making technical concepts accessible to everyone.