Next.js + Supabase Auth: GitHub Login Made Easy
Hey guys! Ever wanted to add a super slick GitHub login to your Next.js app? Well, you're in the right place! Today, we're diving deep into using Supabase Auth Helpers to seamlessly integrate GitHub authentication into your Next.js projects. It's not as scary as it sounds, and trust me, once you get the hang of it, you'll be adding social logins to all your apps. We'll cover everything from setting up your Supabase project to writing the Next.js code that makes it all happen. Get ready to level up your authentication game!
Setting Up Your Supabase Project for GitHub Auth
Alright, first things first, you need a Supabase project up and running. If you don't have one yet, head over to Supabase.io and create a free account. Once you're in, navigate to your project dashboard. We need to enable GitHub as a third-party provider. You'll find this under the 'Authentication' section, then 'Providers'. Click on 'GitHub', and you'll see a couple of fields: 'Client ID' and 'Client Secret'.
To get these, you'll need to create a new OAuth App on GitHub. Go to your GitHub settings, then 'Developer settings', and click 'New OAuth App'. Fill in the details: the Application name can be anything, like 'My Next.js App Auth'. The Homepage URL should be your local development URL (e.g., http://localhost:3000) or your deployed app's URL. The crucial part is the 'Authorization callback URL'. This needs to match exactly what Supabase expects. For Supabase, it's typically https://<your-supabase-url>.supabase.co/auth/v1/callback. Make sure you replace <your-supabase-url> with your actual Supabase project URL. After creating the app on GitHub, you'll get your 'Client ID' and 'Client Secret'. Copy these and paste them back into your Supabase project's GitHub provider settings. Save it, and boom! You've configured GitHub authentication in Supabase.
Integrating Supabase Auth Helpers into Next.js
Now, let's get to the Next.js side of things. Supabase provides fantastic Auth Helpers that make this whole process a breeze. First, you'll need to install the Supabase client library if you haven't already: npm install @supabase/supabase-js. Next, you need to set up your Supabase client instance. A common place for this is a utils/supabaseClient.js file. Here, you'll initialize your client using your Supabase URL and anon key, which you can find in your project settings under 'API'.
// utils/supabaseClient.js
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
export const supabase = createClient(supabaseUrl, supabaseAnonKey);
Make sure to add your Supabase URL and Anon Key to your .env.local file as NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY respectively. Now, for the magic of the Auth Helpers. Supabase provides a NextSupabaseUI package which includes components and hooks to simplify authentication flows.
Install it: npm install @supabase/auth-helpers-nextjs @supabase/auth-ui-react. These helpers are designed to work with Next.js's Server Components and Client Components, offering flexibility. You'll likely want to wrap your application with a SessionContextProvider from @supabase/auth-helpers-react. This context provider makes the Supabase session available throughout your React tree. In your _app.js or _app.tsx file, you'll set this up.
// pages/_app.js
import { SessionContextProvider } from '@supabase/auth-helpers-react';
import { supabase } from '../utils/supabaseClient';
function MyApp({ Component, pageProps }) {
return (
<SessionContextProvider supabaseClient={supabase}>
<Component {...pageProps} />
</SessionContextProvider>
);
}
export default MyApp;
This setup ensures that whenever a user logs in or logs out, the session state is managed globally and reactively. It's super handy for building dynamic UIs that respond to authentication status.
Implementing the GitHub Sign-In Button
With the Supabase client initialized and the SessionContextProvider in place, we can now create the actual GitHub sign-in button. This is where the auth-helpers-nextjs package really shines. You can create a simple React component for your login button.
// components/Auth.js
import React from 'react';
import { useSupabaseClient } from '@supabase/auth-helpers-react';
function AuthButtons() {
const supabase = useSupabaseClient();
const handleLoginWithGithub = async () => {
const { error } = await supabase.auth.signInWithOAuth({
provider: 'github',
});
if (error) {
console.error('Error logging in with GitHub:', error);
}
};
return (
<button onClick={handleLoginWithGithub}>
Sign in with GitHub
</button>
);
}
export default AuthButtons;
This component uses the useSupabaseClient hook to get access to the Supabase client instance. The handleLoginWithGithub function calls supabase.auth.signInWithOAuth and specifies 'github' as the provider. When the button is clicked, this function is executed, initiating the OAuth flow. Supabase handles the redirect to GitHub for user authorization, and after the user approves, GitHub redirects them back to your specified callback URL, where Supabase takes over to manage the session.
To display this button, simply import and render the AuthButtons component in any of your Next.js pages or components. For example, in your pages/index.js:
// pages/index.js
import AuthButtons from '../components/Auth';
export default function Home() {
return (
<div>
<h1>Welcome!</h1>
<AuthButtons />
</div>
);
}
This is the most basic implementation. You can style the button however you like to match your application's design. Remember, the redirect happens automatically. Your users will be sent to GitHub, authenticate, and then be brought back to your app, logged in.
Handling Authentication State and User Data
Once a user successfully logs in via GitHub, their session information is stored by Supabase and can be accessed within your Next.js application. The SessionContextProvider we set up earlier provides a session object that contains details about the currently logged-in user. You can use the useSession hook from @supabase/auth-helpers-react to get this session data.
// components/UserProfile.js
import React from 'react';
import { useSession, useSupabaseClient } from '@supabase/auth-helpers-react';
function UserProfile() {
const session = useSession();
const supabase = useSupabaseClient();
if (session) {
// User is logged in
const user = session.user;
return (
<div>
<p>Welcome, {user.user_metadata.full_name || user.email}!</p>
{/* Display user avatar if available */}
{user.user_metadata.avatar_url && (
<img src={user.user_metadata.avatar_url} alt="User Avatar" width="50" />
)}
<button onClick={() => supabase.auth.signOut()}>Sign out</button>
</div>
);
} else {
// User is not logged in
return <p>Please sign in.</p>;
}
}
export default UserProfile;
In this UserProfile component, we conditionally render content based on whether a session object exists. If the user is logged in, we can access their details from session.user. For GitHub authentication, user.user_metadata often contains useful information like full_name, avatar_url, and public_repos. We also include a 'Sign out' button that calls supabase.auth.signOut(), which clears the session and redirects the user.
To make this work seamlessly, you'd integrate UserProfile into your main application layout or a protected route. You can also create a custom hook or a higher-order component to protect routes, ensuring that only logged-in users can access certain pages. For example, a withAuth HOC could check for a valid session before rendering the protected page component.
Protecting Routes: A common pattern is to check the session status on the server-side using Supabase's server-side helpers or on the client-side during initial load. With Next.js App Router, you can use server-side logic directly in Server Components to fetch session data and decide what to render. For the Pages Router, you might use getServerSideProps to check the session and redirect unauthenticated users.
User Metadata: The user_metadata object is populated by Supabase based on the information provided by the OAuth provider. For GitHub, you'll typically find the user's name, avatar URL, and potentially other public profile details. This data is incredibly useful for personalizing the user experience within your application. Always check what metadata is available and handle cases where certain fields might be missing.
Advanced Scenarios and Best Practices
So far, we've covered the basics, but let's talk about some more advanced stuff and best practices to make your authentication even more robust. Row Level Security (RLS) is a cornerstone of Supabase security. Once a user is logged in, you'll want to ensure they can only access their own data. You can configure RLS policies on your database tables. For instance, in a profiles table, you might have a policy that allows users to select their own profile data based on their auth.uid() (User ID).
Error Handling: The examples above show basic console.error for login failures. In a production app, you'll want to provide user-friendly error messages. Maybe display a toast notification or an inline error message next to the login button. The signInWithOAuth function returns an error object, which you should inspect to understand why the login failed (e.g., invalid callback URL, network issues, user denied access).
Managing Redirects: After a successful login, Supabase automatically redirects the user back to the callback URL. However, you often want to redirect them to a specific page after login, like their dashboard. You can pass a redirectTo option to the signInWithOAuth function:
const { error } = await supabase.auth.signInWithOAuth({
provider: 'github',
options: {
redirectTo: 'http://localhost:3000/dashboard',
}
});
Remember to configure these redirectTo URLs in your Supabase project settings as authorized redirect URLs for OAuth.
Using Supabase Hooks: The @supabase/auth-helpers-react library provides hooks like useSession, useUser, and useSupabaseClient. Make sure you're using these hooks within components wrapped by SessionContextProvider to get the most up-to-date authentication state. Trying to access session data outside this context might lead to stale or undefined values.
Server-Side Rendering (SSR) with Auth: For Next.js, especially if you're using the Pages Router, fetching the session in getServerSideProps is crucial for SSR. You can use createServerSupabaseClient from @supabase/auth-helpers-nextjs to get a Supabase client instance that can read cookies set by Supabase, allowing you to check the user's session on the server before rendering the page.
// pages/dashboard.js
import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs';
export async function getServerSideProps(context) {
const supabase = createServerSupabaseClient(context);
const { data: { session } } = await supabase.auth.getSession();
if (!session) {
// If no session, redirect to login page
return {
redirect: {
destination: '/login',
permanent: false,
},
};
}
// If session exists, pass session data to the page
return {
props: {
initialSession: session,
user: session.user,
},
};
}
// Then in your page component, you can use initialSession
function Dashboard({ user }) {
return <div>Welcome to your dashboard, {user.email}!</div>;
}
export default Dashboard;
For the App Router, the approach is slightly different, often involving Server Components fetching data directly, and Client Components using hooks like useSession for client-side interactivity. Security is paramount: always validate user input and use Supabase's RLS policies to protect your data. Never expose your service_role key on the client-side.
Testing: Thoroughly test your authentication flow. Test sign-in, sign-out, and what happens when users try to access protected routes without being logged in. Test edge cases like network interruptions or GitHub revoking access. Ensure your callback URLs are correctly configured in both Supabase and GitHub.
By following these best practices, you can build a secure, user-friendly authentication system for your Next.js application using Supabase and GitHub integration. Happy coding, everyone!