logo
Published on

Integrating Next-Auth in a Streaming AI Chat Assistant Using Material-UI

Authors
  • avatar
    Name
    Athos Georgiou
    Twitter

Integrating Next-Auth in a Streaming AI Chat Assistant Using Material-UI

integrate-next-js-authentication

Introduction

One of the fundamental aspects of writing applications is authentication. It is the process of verifying the identity of a user and granting them access to protected resources. In this guide in the series, we will be integrating next-auth in a streaming AI chat assistant built with Next.js.

Additionally, we will be using Material-UI for styling our application. Material-UI is a React UI framework that provides a set of components for building responsive web applications. It also provides a theming solution for customizing the look and feel of your application.

Do keep in mind that the code in this guide is indicative and may have differences from the final code.

If you prefer to skip the guide and get the code yourself, you can find it on GitHub

You can also access and test the app directly on Vercel

Prerequisites

  • React and Next.js knowledge
  • Material-UI in your project
  • GitHub and Google accounts for OAuth

Step-by-Step Guide

Step 1: Acquiring OAuth Credentials

GitHub OAuth Setup

  1. Visit GitHub Developer Settings.
  2. Click "New OAuth App".
  3. Enter the required information.
  4. Note the Client ID and Client Secret.
  5. Add http://localhost:3000/api/auth/callback/github to the "Authorization callback URL".

Google OAuth Setup

  1. Go to Google Cloud Console.
  2. Create a new project.
  3. Navigate to "Credentials" and create an OAuth client ID.
  4. Note the Client ID and Client Secret.
  5. Add http://localhost:3000/api/auth/callback/google to the "Authorized redirect URIs".

Step 2: Configuring Environment Variables

Create .env.local with:

GITHUB_ID=your_github_client_id
GITHUB_SECRET=your_github_client_secret
GOOGLE_ID=your_google_client_id
GOOGLE_SECRET=your_google_client_secret
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=a_random_secret_string

Step 3: Setting Up Authentication Providers

Create a new auth route: app/api/auth/[...nextauth]/route.ts:

import NextAuth from 'next-auth/next';
import type { NextAuthOptions } from 'next-auth';
import GitHubProvider from 'next-auth/providers/github';
import GoogleProvider from 'next-auth/providers/google';

const providers = [
  GitHubProvider({
    clientId: process.env.GITHUB_ID as string,
    clientSecret: process.env.GITHUB_SECRET as string,
  }),
  GoogleProvider({
    clientId: process.env.GOOGLE_ID as string,
    clientSecret: process.env.GOOGLE_SECRET as string,
  }),
];

const options: NextAuthOptions = { providers };

const handler = NextAuth(options);
export { handler as GET, handler as POST };

Step 4: Creating AppBar with Material-UI

Use Material-UI for the AppBar, creating a new responsive component: app/components/AppBar/ResponsiveAppBar.tsx:

import * as React from 'react';
import { useSession, signIn, signOut } from 'next-auth/react';
import AppBar from '@mui/material/AppBar';
import Box from '@mui/material/Box';
import Toolbar from '@mui/material/Toolbar';
import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography';
import Menu from '@mui/material/Menu';
import MenuIcon from '@mui/icons-material/Menu';
import Container from '@mui/material/Container';
import Avatar from '@mui/material/Avatar';
import Button from '@mui/material/Button';
import Tooltip from '@mui/material/Tooltip';
import MenuItem from '@mui/material/MenuItem';
import BlurOnRoundedIcon from '@mui/icons-material/BlurOnRounded';
import styles from './ResponsiveAppBar.module.css';
const pages = ['Home', 'About'];
const settings = ['Account', 'Logout'];

function ResponsiveAppBar() {
  const { data: session } = useSession();
  const [anchorElNav, setAnchorElNav] = React.useState<null | HTMLElement>(
    null
  );
  const [anchorElUser, setAnchorElUser] = React.useState<null | HTMLElement>(
    null
  );

  const handleOpenNavMenu = (event: React.MouseEvent<HTMLElement>) => {
    setAnchorElNav(event.currentTarget);
  };
  const handleOpenUserMenu = (event: React.MouseEvent<HTMLElement>) => {
    setAnchorElUser(event.currentTarget);
  };

  const handleCloseNavMenu = () => {
    setAnchorElNav(null);
  };

  const handleCloseUserMenu = () => {
    setAnchorElUser(null);
  };

  const handleLogout = () => {
    signOut();
  };

  const handleLogin = () => {
    signIn();
  };

  return (
    <AppBar position="fixed" className={styles.main}>
      <Container maxWidth="xl">
        <Toolbar disableGutters>
          <BlurOnRoundedIcon
            sx={{ display: { xs: 'none', md: 'flex' }, mr: 1 }}
          />
          <Typography
            variant="h6"
            noWrap
            component="a"
            href="#responsive-app-bar"
            sx={{
              mr: 2,
              display: { xs: 'none', md: 'flex' },
              fontFamily: 'monospace',
              fontWeight: 700,
              letterSpacing: '.3rem',
              color: 'inherit',
              textDecoration: 'none',
            }}
          >
            Titanium
          </Typography>

          <Box sx={{ flexGrow: 1, display: { xs: 'flex', md: 'none' } }}>
            <IconButton
              size="large"
              aria-label="account of current user"
              aria-controls="menu-appbar"
              aria-haspopup="true"
              onClick={handleOpenNavMenu}
              color="inherit"
            >
              <MenuIcon />
            </IconButton>
            <Menu
              id="menu-appbar"
              anchorEl={anchorElNav}
              anchorOrigin={{
                vertical: 'bottom',
                horizontal: 'left',
              }}
              keepMounted
              transformOrigin={{
                vertical: 'top',
                horizontal: 'left',
              }}
              open={Boolean(anchorElNav)}
              onClose={handleCloseNavMenu}
              sx={{
                display: { xs: 'block', md: 'none' },
              }}
            >
              {pages.map((page) => (
                <MenuItem key={page} onClick={handleCloseNavMenu}>
                  <Typography textAlign="center">{page}</Typography>
                </MenuItem>
              ))}
            </Menu>
          </Box>
          <BlurOnRoundedIcon
            sx={{ display: { xs: 'flex', md: 'none' }, mr: 1 }}
          />
          <Typography
            variant="h5"
            noWrap
            component="a"
            href="#app-bar-with-responsive-menu"
            sx={{
              mr: 2,
              display: { xs: 'flex', md: 'none' },
              flexGrow: 1,
              fontFamily: 'monospace',
              fontWeight: 700,
              letterSpacing: '.3rem',
              color: 'inherit',
              textDecoration: 'none',
            }}
          >
            Titanium
          </Typography>
          <Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
            {pages.map((page) => (
              <Button
                key={page}
                onClick={handleCloseNavMenu}
                sx={{ my: 2, color: 'white', display: 'block' }}
              >
                {page}
              </Button>
            ))}
          </Box>

          <Box sx={{ flexGrow: 0 }}>
            <Tooltip title="Open settings">
              <IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
                <Avatar alt="User Avatar" src={session?.user?.image || ''} />
              </IconButton>
            </Tooltip>
            <Menu
              sx={{ mt: '45px' }}
              id="menu-appbar"
              anchorEl={anchorElUser}
              anchorOrigin={{
                vertical: 'top',
                horizontal: 'right',
              }}
              keepMounted
              transformOrigin={{
                vertical: 'top',
                horizontal: 'right',
              }}
              open={Boolean(anchorElUser)}
              onClose={handleCloseUserMenu}
            >
              {session ? (
                settings.map((setting) => (
                  <MenuItem
                    key={setting}
                    onClick={
                      setting === 'Logout' ? handleLogout : handleCloseUserMenu
                    }
                  >
                    <Typography textAlign="center">{setting}</Typography>
                  </MenuItem>
                ))
              ) : (
                <MenuItem onClick={handleLogin}>
                  <Typography textAlign="center">Log in</Typography>
                </MenuItem>
              )}
            </Menu>
          </Box>
        </Toolbar>
      </Container>
    </AppBar>
  );
}
export default ResponsiveAppBar;

Step 5: Plugging in...

Import the new AppBar and then wrap your app/page.tsx in a SessionProvider:

'use client'

import React from 'react'
import { SessionProvider } from 'next-auth/react'

import ResponsiveAppBar from './components/AppBar/ResponsiveAppBar'
import Chat from './components/Chat/Chat'
import styles from './page.module.css'

export default function Home() {
  return (
    <SessionProvider>
      <ResponsiveAppBar />
      <main className={styles.main}>
        <Chat />
      </main>
    </SessionProvider>
  )
}

Step 6: Adding Session Protection

Add session protection in app/components/Chat/Chat.tsx:

if (session) {
  return (
    <>
      {isLoading && <Loader />}
      <MessagesField messages={messages} />
      <div className={styles.inputArea}>
        <CustomizedInputBase setIsLoading={setIsLoading} onSendMessage={sendUserMessage} />
      </div>
    </>
  )
}
return (
  <div className={styles.loginPrompt}>
    <p>Please sign in to access the chat.</p>
  </div>
)

That's It!

I hope you found this guide useful and that it helped you integrate next-auth in your Next.js project. I know I had a blast coding so far and I'm looking forward to all the cool stuff we'll be doing in the future.

More specifically, now that we have streaming chat and authentication set up in Titanium, we can start working on more advanced concepts, such as: Multi-user persistent memory, RAG, Assistants, Vision, and Speech.

If you have any questions or comments, feel free to reach out to me on GitHub, LinkedIn, or via email.

See ya around and happy coding!