Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

provide an example with i18n #9536

Open
stefanprobst opened this issue Jan 4, 2024 · 10 comments
Open

provide an example with i18n #9536

stefanprobst opened this issue Jan 4, 2024 · 10 comments
Labels
docs Relates to documentation good first issue Good issue to take for first time contributors

Comments

@stefanprobst
Copy link

What is the improvement or update you wish to see?

it would be super helpful to have an official example repo which shows how to combine next-auth v5 with an i18n library like next-intl.

from the current docs, it is not clear to me

  • how to correctly compose auth and i18n middleware
  • how to get translated sign-in/sign-out pages

thanks 🙏

Is there any context that might help us understand?

Does the docs page already exist? Please link to it.

No response

@stefanprobst stefanprobst added docs Relates to documentation triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime. labels Jan 4, 2024
@jero237
Copy link

jero237 commented Jan 7, 2024

Hi! After many hours I've managed to create my own auth middleware to work properly alongside next-intl. Here is the code:

Middleware

import { auth } from "@/auth";
import pages from "./lib/pages";
import { NextRequest, NextResponse } from "next/server";
import createIntlMiddleware from "next-intl/middleware";

const locales = ["en", "es"];
const protectedPages = ["/dashboard/*"];
const authPages = ["/auth/signin", "/auth/signup"];

const intlMiddleware = createIntlMiddleware({
  locales,
  defaultLocale: "es",
  localePrefix: "as-needed",
});

const testPagesRegex = (pages: string[], pathname: string) => {
  const regex = `^(/(${locales.join("|")}))?(${pages
    .map((p) => p.replace("/*", ".*"))
    .join("|")})/?$`;
  return new RegExp(regex, "i").test(pathname);
};

const handleAuth = async (
  req: NextRequest,
  isAuthPage: boolean,
  isProtectedPage: boolean,
) => {
  const session = await auth();
  const isAuth = !!session?.user;

  if (!isAuth && isProtectedPage) {
    let from = req.nextUrl.pathname;
    if (req.nextUrl.search) {
      from += req.nextUrl.search;
    }

    return NextResponse.redirect(
      new URL(
        `${pages.auth.signin()}?from=${encodeURIComponent(from)}`,
        req.url,
      ),
    );
  }

  if (isAuth && isAuthPage) {
    return NextResponse.redirect(new URL(pages.dashboard.root, req.nextUrl));
  }

  return intlMiddleware(req);
};

export default async function middleware(req: NextRequest) {
  const isAuthPage = testPagesRegex(authPages, req.nextUrl.pathname);
  const isProtectedPage = testPagesRegex(protectedPages, req.nextUrl.pathname);

  return await handleAuth(req, isAuthPage, isProtectedPage);
}

export const config = {
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};

use of signIn()

  const searchParams = useSearchParams();

  const handleGoogle = async () => {
    setLoading(true);
    return await signIn("google", {
      callbackUrl: searchParams?.get("from") || pages.dashboard.root,
    });
  };

@Adherentman
Copy link

Adherentman commented Jan 16, 2024

@jero237 How to deal with callbackUrl in signin?

signin("credentials", {
      email: formData.get("email"),
      password: formData.get("password"),
      redirect: true,
      callbackUrl: searchParams?.get("callbackUrl") || "/",
    });

this code alwalys redirect pages.dashboard.root

@jero237
Copy link

jero237 commented Jan 16, 2024

@jero237 How to deal with callbackUrl in signin?

signin("credentials", {
      email: formData.get("email"),
      password: formData.get("password"),
      redirect: true,
      callbackUrl: searchParams?.get("callbackUrl") || "/",
    });

this code alwalys redirect pages.dashboard.root

great question, I've just updated the comment above

@Adherentman
Copy link

Adherentman commented Jan 18, 2024

@jero237 I change some code with signin after redirect by callbackUrl

  if (isAuth && isAuthPage) {
    const url = req.nextUrl.clone();
    const fromValue = url.searchParams.get("from");
    return NextResponse.redirect(new URL(fromValue ?? "/", req.nextUrl));
  }

This is my all code

const handleAuth = async (req: NextRequest, isAuthPage: boolean, isProtectedPage: boolean) => {
  const session = await auth();
  const isAuth = !!session?.user;

  if (!isAuth && isProtectedPage) {
    let from = req.nextUrl.pathname;
    if (req.nextUrl.search) {
      from += req.nextUrl.search;
    }
    return NextResponse.redirect(new URL(`/auth/signin?from=${encodeURIComponent(from)}`, req.url));
  }

  if (isAuth && isAuthPage) {
    const url = req.nextUrl.clone();
    const fromValue = url.searchParams.get("from");
    return NextResponse.redirect(new URL(fromValue ?? "/", req.nextUrl));
  }

  return intlMiddleware(req);
};

export default async function middleware(req: NextRequest) {
  const isAuthPage = testPagesRegex(authPages, req.nextUrl.pathname);
  const isProtectedPage = testPagesRegex(protectedPages, req.nextUrl.pathname);

  return await handleAuth(req, isAuthPage, isProtectedPage);
}

@balazsorban44 balazsorban44 added good first issue Good issue to take for first time contributors and removed triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime. labels Jan 25, 2024
@soapproject
Copy link

soapproject commented May 28, 2024

Same requirement from i18next

import { withAuth } from 'next-auth/middleware';
import acceptLanguage from 'accept-language';

const cookieName = 'i18next';
const fallbackLng = 'en';
const languages = ['en', 'zh'];

// Language from cookie/headers/fallback
function getLng(req: NextRequest) {
  if (req.cookies.has(cookieName))
    return acceptLanguage.get(req.cookies.get(cookieName)?.value);

  return acceptLanguage.get(req.headers.get('Accept-Language')) ?? fallbackLng;
};

// Append i18n language to path like: app/[lng]/social-login/page.tsx
// example: /social-login -> /en/social-login
function handleI18n(req: NextRequest) {
  if (req.nextUrl.pathname.startsWith('/api')) return;

  if (
    !languages.some(loc => req.nextUrl.pathname.startsWith(`/${loc}`)) &&
    !req.nextUrl.pathname.startsWith('/_next')
  ) {
    const lng = getLng(req);
    return NextResponse.redirect(
      new URL(`/${lng}${req.nextUrl.pathname}`, req.url)
    );
  }

  if (req.headers.has('referer')) {
    const refererUrl = new URL(req.headers.get('referer')!);
    const lngInReferer = languages.find(l =>
      refererUrl.pathname.startsWith(`/${l}`)
    );
    const response = NextResponse.next();
    if (lngInReferer)
      response.cookies.set(cookieName, lngInReferer, { sameSite: 'lax' });
    return response;
  }
}

function middleware(req: NextRequest) {
  console.log('Running middleware!'); // <--------- This will not run if not authed
  return handleI18n(req) ?? NextResponse.next();
}

export default withAuth(middleware, {
  pages: {
    signIn: '/social-login', // <--------- This will not redirect to /en/social-login
    signOut: '/social-login',
  },
});

export const config = {
  matcher: ['/((?!_next/static|_next/image|assets|favicon.ico|sw.js|fonts).*)'],
};

will be nice if we can access req in NextAuthMiddlewareOptions

export default withAuth(middleware, (req) => ({
  pages: {
    signIn: `${getLng(req)}/social-login`,
    signOut: `${getLng(req)}/social-login`,
  },
}));

@soapproject
Copy link

update:
I just notic v5 comming soon and it will be much easyer. But so far this works with v4.

import acceptLanguage from 'accept-language';
import { withAuth, type NextRequestWithAuth } from 'next-auth/middleware';
import { NextResponse, type NextRequest } from 'next/server';
import { cookieName, fallbackLng, languages } from './i18n';

acceptLanguage.languages([...languages]);

export const config = {
matcher: [
  '/((?!_next/static|_next/image|assets|favicon.ico|sw.js|manifest.json|fonts).*)',
],
};

const getLng = (req: NextRequest) => {
if (req.cookies.has(cookieName))
  return acceptLanguage.get(req.cookies.get(cookieName)?.value);

return acceptLanguage.get(req.headers.get('Accept-Language')) ?? fallbackLng;
};

const handleI18n = (req: NextRequest) => {
if (req.nextUrl.pathname.startsWith('/api')) return;

const pathnameStartsWithLanguage = languages.some(loc =>
  req.nextUrl.pathname.startsWith(`/${loc}`)
);
if (
  !pathnameStartsWithLanguage &&
  !req.nextUrl.pathname.startsWith('/_next')
) {
  const lng = getLng(req);
  return NextResponse.redirect(
    new URL(`/${lng}${req.nextUrl.pathname}`, req.url)
  );
}

if (req.headers.has('referer')) {
  const refererUrl = new URL(req.headers.get('referer')!);
  const lngInReferer = languages.find(l =>
    refererUrl.pathname.startsWith(`/${l}`)
  );
  const response = NextResponse.next();
  if (lngInReferer) {
    response.cookies.set(cookieName, lngInReferer, { sameSite: 'lax' });
  }
  return response;
}

return;
};

const handleAuth = (req: NextRequestWithAuth) => {
if (req.nextUrl.pathname.includes('/dmz')) return NextResponse.next();

const token = req.nextauth.token;
if (!token)
  return NextResponse.redirect(new URL(`/dmz/social-login`, req.url));

return;
};

const middleware = (req: NextRequestWithAuth) => {
const i18nRes = handleI18n(req);
if (i18nRes) return i18nRes;

const authRes = handleAuth(req);
if (authRes) return authRes;

return NextResponse.next();
};

export default withAuth(middleware, {
callbacks: {
  authorized() {
    /**
     * Trick, tell next-auth we are always authorized, so middleware callback will not skip.
     * then we handle token check and redirect to login page our self.
     */
    return true;
  },
},
});

@ScreamZ
Copy link

ScreamZ commented Jun 25, 2024

@balazsorban44 save us please 🥺

@phibo23
Copy link

phibo23 commented Aug 5, 2024

There is an example by the maintainer of next-intl https://github.com/amannn/next-intl/blob/main/examples/example-app-router-next-auth/src/middleware.ts (for v4)

@barlas

This comment has been minimized.

@ScreamZ
Copy link

ScreamZ commented Nov 15, 2024

There is an example by the maintainer of next-intl amannn/next-intl@main/examples/example-app-router-next-auth/src/middleware.ts (for v4)

I left the ship for Lucia Auth, no more issue but thanks :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs Relates to documentation good first issue Good issue to take for first time contributors
Projects
None yet
Development

No branches or pull requests

8 participants