Getting started

User authentication

Pre-requisites needed

You will need a database for this section, please make sure that you've set up a database before you start this guide.

If you want to implement Magic Links, you will need to have configured emails in order for it to work.

SupaSaaS uses the tried and tested community favourite, NextAuth, to handle user authentication.

NextAuth allows us to create two types of authentication workflows:

  • Social Login (which includes GitHub, Google, Facebook & 60+ more providers)
  • Magic Links (passwordless login).

It's completely up to you which type you use, or what providers you want to offer. In this guide, I'll show you how to implement both.


NextAuth

Configuration

The authentication logic for NextAuth is contained in the file @/config/auth.ts. In this file, you can configure what auth providers you want to use.

NextAuth requires 2 environment variables to work, make sure to fill these in like below.

.env
NEXTAUTH_URL="http://localhost:3000" NEXTAUTH_SECRET="4348yhu34h3ui4ofjndfsdfeirh4b637u5sfd3"

The NEXTAUTH_SECRET is a random string of at least 10 characters which is used to encrypt authentication data.

You can generate a NextAuth secret key using this free tool.

Environment variables

Make sure that you don't prefix your environment variables with NEXT_PUBLIC_ as this will expose them to the client side and be vulnerable to security risks.

Magic Links are a great way to allow users to log in without needing to remember a password.

They work by sending an email to the user with a link that they can click to log in.

Loops setup

Add an EmailProvider to the authOptions configuration object. This will allow NextAuth to send emails using the Loops API.

@/src/auth.ts
import { sendTransactionalEmailWithLoops } from "@/lib/email/loops"; providers: [ EmailProvider({ async sendVerificationRequest({ identifier: email, url }) { // Send transactional with Loops await sendTransactionalEmailWithLoops({ transactionalId: process.env.NEXT_PUBLIC_LOOPS_TRANSACTIONAL_ID || "", // the transactional id you created on Loops email, dataVariables: { url, // the dynamic url we setup in the "Setup emails" section }, }); }, }), ],

Mailchimp setup

Once you've got the API key, we need to add it to your environment variables.

Head over to your .env file and paste it into the LOOPS_API_KEY variable. It should look like this:

@/src/auth.ts
import { emailFrom } from "@/config/config.ts"; import { sendTransactionalEmailWithMailchimp } from "@/lib/email/mailchimp"; providers: [ EmailProvider({ await sendTransactionalEmailWithMailchimp({ to: [email], from: emailFrom, subject: "Sign In to MyApp", text: `Please click here to authenticate - ${url}`, html: `<p>Please click <a href="${url}">here</a> to authenticate</p>`, }); }), ],

Social Login

Google

To enable Google login, you need to create a new project on the Google Developer Console.

Once you've created a new project, head over to the OAuth consent screen and fill in the necessary details.

After that, go to the Credentials tab and create a new OAuth 2.0 Client ID. Make sure to add http://localhost:3000/api/auth/callback/google as an authorized redirect URI.

Once you've created the client ID, copy the Client ID and Client Secret and add them to your environment variables.

.env
GOOGLE_CLIENT_ID="your google client id" GOOGLE_CLIENT_SECRET="your google client secret"

GitHub

To enable GitHub login, you need to create a new OAuth app on the GitHub Developer Settings page.

Fill in the necessary details and add http://localhost:3000/api/auth/callback/github as the callback URL.

Once you've created the OAuth app, copy the Client ID and Client Secret and add them to your environment variables.

.env
GITHUB_ID="your github client id" GITHUB_SECRET="your github client secret"

Facebook

To enable Facebook login, you need to create a new app on the Facebook Developer Console.

Fill in the necessary details and add http://localhost:3000/api/auth/callback/facebook as the callback URL.

Once you've created the app, copy the App ID and App Secret and add them to your environment variables.

.env
FACEBOOK_ID="your facebook app id" FACEBOOK_SECRET="your facebook app secret"

Twitter

To enable Twitter login, you need to create a new app on the Twitter Developer Console.

Fill in the necessary details and add http://localhost:3000/api/auth/callback/twitter as the callback URL.

Once you've created the app, copy the API Key and API Secret Key and add them to your environment variables.

.env
TWITTER_ID="your twitter api key" TWITTER_SECRET="your twitter api secret key"

Enabling providers

NextAuth supports over 60+ providers, you can find the full list here.

You can have as many or as little providers enabled at any time as you like. Simply add them to the providers array in the authOptions object in the @/config/auth.ts file like below.

@/config/auth.ts
import GitHubProvider from "next-auth/providers/github"; import GoogleProvider from "next-auth/providers/google"; import FacebookProvider from "next-auth/providers/facebook"; import TwitterProvider from "next-auth/providers/twitter"; providers: [ GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID || "", clientSecret: process.env.GOOGLE_CLIENT_SECRET || "", }), GitHubProvider({ clientId: process.env.GITHUB_ID || "", clientSecret: process.env.GITHUB_SECRET || "", }), FacebookProvider({ clientId: process.env.FACEBOOK_ID || "", clientSecret: process.env.FACEBOOK_SECRET || "", }), TwitterProvider({ clientId: process.env.TWITTER_ID || "", clientSecret: process.env.TWITTER_SECRET || "", }), ],

You can also have Magic Links authentication enabled alongside social login providers. Simply add the EmailProvider to the providers array like below.

@/config/auth.ts
import EmailProvider from "next-auth/providers/email"; import GitHubProvider from "next-auth/providers/github"; import GoogleProvider from "next-auth/providers/google"; import FacebookProvider from "next-auth/providers/facebook"; import TwitterProvider from "next-auth/providers/twitter"; providers: [ EmailProvider({ async sendVerificationRequest({ identifier: email, url }) { // Send transactional with Loops await sendTransactionalEmailWithLoops({ transactionalId: process.env.NEXT_PUBLIC_LOOPS_TRANSACTIONAL_ID || "", // the transactional id you created on Loops email, dataVariables: { url, // the dynamic url we setup in the "Setup emails" section }, }); }, }), GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID || "", clientSecret: process.env.GOOGLE_CLIENT_SECRET || "", }), GitHubProvider({ clientId: process.env.GITHUB_ID || "", clientSecret: process.env.GITHUB_SECRET || "", }), FacebookProvider({ clientId: process.env.FACEBOOK_ID || "", clientSecret: process.env.FACEBOOK_SECRET || "", }), TwitterProvider({ clientId: process.env.TWITTER_ID || "", clientSecret: process.env.TWITTER_SECRET || "", }), ],

SupaSaas comes with a set of pre-built components that you can use to allow users to sign in and sign up.

You can find these components in the @/components/Auth directory.

Sign In & Sign Out Pages

Pre-built and configured sign in @/app/(frontend)/signin and sign up @/app/(frontend)/signup pages are available for you to customize.

Social Login Buttons

I also provide a SocialAuthSignInButton component that you can use to allow users to sign in with their social accounts from anywhere in your app.

To use it, just import it and render with the provider as the prop.

example.tsx
import SocialAuthSignInButton from "@/components/Auth/Buttons/SocialAuthSignInButton"; <div className="flex flex-col gap-2"> <SocialAuthSignInButton provider="google" /> <SocialAuthSignInButton provider="github" /> </div>

Auth State Management

SupaSaaS uses Zustand for state management. It's a simple and fast state management solution that allows you to manage global state in your application.

I've implemented an authStore that you can use to house all logic related to user authentication, which can then be accessed by any component from anywhere in your app with just 2 lines of code. It's pretty awesome!

You can find the authStore in the @/store/auth-store.ts file.

In this file, you'll find the getSubscriptionInfo function which handles setting the users subscription status.

Set your payment provider

You need to comment/uncomment the payment provider that you're using in the getSubscriptionInfo function.

@/store/auth-store.ts
getSubscriptionInfo: async () => { // const data = await getLemonSqueezyPlan(); const data = await getStripePlan(); // ... rest of function }

Plan properties

It's in this file that the logic is stored which is responsible for checking the users active plan, and setting what plan properties they have access to.

For example, in a "Basic Plan" a user might have access to upload 10 images, whereas in a "Pro Plan" they might have access to upload 100 images.

Please refer to the create a database guide and configure payments guide for more information on how the model relationships are set up to handle this.

Inside the getSubscriptionInfo function, scroll down until you see this code:

@/store/auth-store.ts
// Loop through the plan properties and set the values for the user // Do this for each property your app needs to set, or implement your own logic if it's alot. data.planProperties.forEach((property: ProductPlanProperty) => { if (property.propertyName === "MAX_IMAGES") { set({ planMaxImages: property.value }); } });

This is where you can set the plan properties for the user. Make sure that each property name matches the name of a ProductPlanPropertyName in your database.

In our example, MAX_IMAGES is a property that we set for the user, which is the maximum number of images they can upload.

Setting plan properties

First, you need to update the AuthStore type to include the new property. In this example, let's add a property which limits the number of domains our user can add to some feature within our app.

In your database, you would have a ProductPlanPropertyName called MAX_DOMAINS which you can set for each plan. Please refer to the configure payments guide for more information on how to set this up.

@/store/auth-store.ts
type AuthStore { planMaxImages: number; planMaxDomains: number; // Add property name here }

Then, you need to update the initialState object to include the new property.

@/store/auth-store.ts
const useAuthStore = create<AuthStore>((set) => ({ planName: "", planStatus: "", planProperties: [], planCanRemoveBranding: false, planMaxImages: 0, planMaxDomains: 0, // Add the initial state value here billingPortal: "", // ... rest of function }));

Finally, you need to update the getSubscriptionInfo function to set the new property for the user.

@/store/auth-store.ts
data.planProperties.forEach((property: ProductPlanProperty) => { if (property.propertyName === "MAX_IMAGES") { set({ planMaxImages: property.value }); } // Add the new property here and set the planMaxDomains value if (property.propertyName === "MAX_DOMAINS") { set({ planMaxDomains: property.value }); } });

Accessing auth state

You can access the auth state from any component in your app with just 2 lines of code by importing the useAuthStore hook and calling it like below.

example.tsx
import { useAuthStore } from "@/store/auth-store"; const { planStatus, planMaxImages, planMaxDomains } = useAuthStore();

You can then use these values in your component like below.

example.tsx
<div> <p>Plan status: {planStatus}</p> <p>Max images: {planMaxImages}</p> <p>Max domains: {planMaxDomains}</p> </div>

useAppAuth hook

I've also created a custom hook called useAppAuth that you can use.

This will return you ALL the necessary authentication properties that you could possibly need for managing your app.

The hook will return you the following properties:

requiredPlanName userPlanName userPlanProperties userPlanMaxImages userPlanCanRemoveBranding userPlanBillingPortal isLoggingIn isLoggedIn isLoggedOut isPlanActive loggedInEmail loggedInName loggedInImage loggedInId getSubscriptionInfo

You can find this hook in the @/hooks/useAppAuth.ts file.

You can use this hook in any component where you need to check auth status like below.

example.tsx
import { useAppAuth } from "@/hooks/useAppAuth"; const { isLoggedIn, isPlanActive, userPlanMaxImages } = useAppAuth(); if (!isLoggedIn) { return <p>You need to be logged in to access this page</p>; } if (!isPlanActive) { return <p>You need to upgrade your plan to access this feature</p>; } return <p>You can upload {userPlanMaxImages} images</p>;
Previous
Configure payments