DEV Community

rashidpbi
rashidpbi

Posted on

πŸ”‘ Figuring Out Authentication in Next.js (While Trying Not to Lose My Sanity πŸ˜…)

I'm going to be honest β€” I'm still figuring out the authentication flow in my Next.js app. Right now, I'm using Google OAuth for a Calendar integration, and every time I think I've nailed it… a token expires and my whole "flawless" login flow collapses.

If you've been down this road, you already know the fun parts:

  • Cookies disappearing like socks in the dryer
  • Google's invalid_grant error appearing out of nowhere
  • That feeling when your API route works only if you log in 5 seconds ago

This post isn't "Here's the perfect authentication guide." It's more like "Here's what I'm doing right now so my app works β€” but I know there are better ways."

The messy reality of OAuth

Most OAuth tutorials basically stop after:

  1. Click "Sign in with Google"
  2. Get access_token
  3. Call an API πŸŽ‰

That's fine for a demo. In real life:

  • Tokens expire (sometimes faster than you expect)
  • Refresh tokens aren't always provided
  • You have to decide where to handle expiry β€” server, client, or both

Spoiler: I learned you actually need both.

My current API route (work in progress)

Here's the route I'm using to fetch Google Calendar events. It's not perfect, but it's keeping my app from falling apart (most of the time).

// /pages/api/eventList.js
import { google } from "googleapis";
import oauth2Client from "@/utils/google-auth";

export default async function handler(req, res) {
  function getFirstDayOfLastMonth() {
  const now = new Date();
  return new Date(now.getFullYear(), now.getMonth() - 1, 1).toISOString();
}
  const {
    google_access_token,
    expiry_date,
    refresh_token_expires_in,
    refresh_token,
  } = req.cookies;
  if (req.method === "GET") {
    try {
      oauth2Client.setCredentials({
        access_token: google_access_token,
        expiry_date,
        refresh_token_expires_in,
        refresh_token,
      });
      const calendar = google.calendar({ version: "v3", auth: oauth2Client });

      const { data } = await calendar.events.list({
        auth: oauth2Client,
        calendarId: "primary",
        // maxResults:13,
         timeMin: getFirstDayOfLastMonth()
        // timeMin: "2025-07-01T00:00:00.000Z",
      });

      res.status(200).json({ events: data.items });
    } catch (error) {
       console.error("Google API error:", error);

  // Normalize error
  let normalizedError = "Unknown error";

  if (error?.response?.data?.error) {
    normalizedError = error.response.data.error;
  } else if (error?.errors?.length) {
    normalizedError = error.errors[0].message;
  } else if (error?.message) {
    normalizedError = error.message;
  }

  res.status(400).json({ error: normalizedError,  code: error?.code || null });
    }
  } else {
    res.setHeader("Allow", ["GET"]);
    res.status(405).end(`Method ${req.method} Not Allowed`);
  }
}

Enter fullscreen mode Exit fullscreen mode

The good:

  • If there's no token, I stop immediately.
  • If Google says the token is bad, I return a 401.

The "still figuring out" parts:

  • I don't refresh tokens automatically yet.
  • I don't update cookies when I do get a new token.
  • My error handling works, but it's not super descriptive.

Client-side "bail out" strategy

Right now, my frontend just looks for 401 and punts the user back to /login.

async function fetchEvents() {
  const res = await fetch("/api/eventList");

  if (res.status === 401) {
    localStorage.setItem("loggedOutDueToTokenIssue", "true");
    window.location.href = "/login";
    return;
  }

  return res.json();
}
Enter fullscreen mode Exit fullscreen mode

It's simple, and it works… but it also means the user gets kicked out even in cases where I could have refreshed the token in the background.

Things I know I need to improve

βœ… Automatic token refresh on the server (before hitting Google's API)

βœ… Updating cookies when I refresh a token

βœ… Returning more structured error messages so the client knows exactly what happened

βœ… Maybe centralizing all token logic so I don't repeat it in every route

Final thoughts (for now)

I'm sharing this because sometimes dev posts make it sound like people go from "I want OAuth" to "I have the perfect auth system" in one weekend. The truth is, I'm still tripping over my own code, but each small improvement makes the whole thing less fragile.

If you've built a rock-solid Google OAuth flow in Next.js β€” especially one that gracefully handles token refresh β€” please drop your tips in the comments. I could really use them. πŸ™ƒ

Top comments (0)