Jump to content

NextAuth.js

Kiedy chcesz doda─ç system kont do swojej aplikacji Next.js, NextAuth.js to znakomite rozwi─ůzanie. Pozwala ono wdro┼╝y─ç z┼éo┼╝one systemy bezpiecze┼ästwa nie zmuszaj─ůc Ci─Ö przy tym do pisania ich w┼éasnor─Öcznie. NextAuth.js zawiera rozleg┼é─ů list─Ö provider├│w, kt├│re zapewni─ů Ci szybki spos├│b na dodanie OAutha. Paczka ta posiada r├│wnie┼╝ wiele adapter├│w dla baz danych i ORM├│w.

Context Provider

W pliku pages/_app.tsx zobaczy─ç mo┼╝esz, i┼╝ twoja aplikacja znajduje si─Ö w SessionProviderzeÔćŚ:

pages/_app.tsx
<SessionProvider session={session}>
  <Component {...pageProps} />
</SessionProvider>

Ten oto provider kontekstu pozwala twojej aplikacji na dost─Öp do danych sesji z ka┼╝dego miejsca, bez potrzeby przesy┼éania ich po ÔÇťpropsachÔÇŁ:

pages/users/[id].tsx
import { useSession } from "next-auth/react";

const User = () => {
  const { data: session } = useSession();

  if (!session) {
    // Obs┼éu┼╝ status nie bycia zalogowanym, np. wy┼Ťwietl komponent `SignIn`
    return <SignIn />;
  }

  return <p>Welcome {session.user.name}!</p>;
};

Otrzymywanie sesji po stronie serwera

Czasem mo┼╝esz chcie─ç otrzyma─ç sesj─Ö na serwerze. Aby to zrobi─ç, pobierz sesj─Ö korzystaj─ůc z funkcji pomocniczej getServerAuthSession dostarczanej przez Create T3 App a nast─Öpnie prze┼Ťlij j─ů do klienta korzystaj─ůc z getServerSideProps:

pages/users/[id].tsx
import { getServerAuthSession } from "../server/auth";
import type { GetServerSideProps } from "next";
export const getServerSideProps: GetServerSideProps = async (ctx) => {
  const session = await getServerAuthSession(ctx);
  return {
    props: { session },
  };
};
const User = () => {
  const { data: session } = useSession();
  // UWAGA: obiekt `session` nie będzie miał stanu ładowania, ponieważ jest już pobrany na serwerze
  ...
}

Do┼é─ůczanie user.id do sesji

Create T3 App jest skonfigurowany tak, aby wykorzysta─ç callback sessionÔćŚ w konfiguracji NextAuth.js do dodania ID u┼╝ytkownika do obiektu session.

pages/api/auth/[...nextauth].ts
callbacks: {
    session({ session, user }) {
      if (session.user) {
        session.user.id = user.id;
      }
      return session;
    },
  },

┼ü─ůczy si─Ö to z plikiem deklaracji typ├│w, aby zapewni─ç odpowiednie typy obiektu session - musi on zawiera─ç powy┼╝sze pole user.id. Wi─Öcej o zmianie typ├│w poczyta─ç mo┼╝esz w rozdziale Module AugmentationÔćŚ dokumentacji NextAuth.js.

types/next-auth.d.ts
import { DefaultSession } from "next-auth";

declare module "next-auth" {
  interface Session {
    user?: {
      id: string;
    } & DefaultSession["user"];
  }
}

Ten sam spos├│b wykorzystany mo┼╝e zosta─ç do dodania wi─Ökszej ilo┼Ťci danych na obiekcie session, takich jak pole role (rola). Nie powinien by─ç on jednak wykorzystany do zapisywania wra┼╝liwych danych dla klienta.

Korzystanie wraz z tRPC

Je┼╝eli u┼╝ywasz NextAuth.js oraz tRPC, stworzy─ç mo┼╝na zabezpieczone procedury (z mo┼╝liwo┼Ťci─ů wielokrotnego u┼╝ycia) korzystaj─ůc z middlewareÔÇÖ├│wÔćŚ. Pozwala to na umieszczenie procedur, kt├│re zainicjowane by─ç mog─ů jedynie przez autoryzowanych u┼╝ytkownik├│w. create-t3-app wszystko to dla Ciebie konfiguruje, daj─ůc ci mo┼╝liwo┼Ť─ç ┼éatwego dost─Öpu do obiektu sesji w┼Ťr├│d autoryzowanych procedur.

Konfiguracja ta zachodzi w dw├│ch krokach:

  1. Pobierz sesj─Ö z header├│w zapytania korzystaj─ůc z funkcji getServerSessionÔćŚ. Nie martw si─Ö, funkcja ta jest bezpieczna do u┼╝ycia - nazwa zawiera s┼éowo unstable jedynie dlatego, poniewa┼╝ implementacja API w przysz┼éo┼Ťci mo┼╝e si─Ö zmieni─ç. Zalet─ů korzystania z getServerSession zamiast getSession jest fakt, i┼╝ jest to funkcja wywo┼éywana jedynie po stronie serwera i nie inicjuje ona ┼╝adnych niepotrzebnych zapyta┼ä. create-t3-app tworzy funkcj─Ö pomocnicz─ů, kt├│ra u┼éatwia korzystanie z getServerSession.
server/auth.ts
export const getServerAuthSession = async (ctx: {
  req: GetServerSidePropsContext["req"];
  res: GetServerSidePropsContext["res"];
}) => {
  return await getServerSession(ctx.req, ctx.res, authOptions);
};

Korzystaj─ůc z tej funkcji pomocniczej, mo┼╝emy otrzyma─ç sesj─Ö i przes┼éa─ç j─ů do kontekstu tRPC:

server/api/trpc.ts
import { getServerAuthSession } from "../common/get-server-auth-session";

export const createContext = async (opts: CreateNextContextOptions) => {
  const { req, res } = opts;
  const session = await getServerAuthSession({ req, res });
  return await createContextInner({
    session,
  });
};
  1. Stw├│rz middleware tRPC, kt├│ry sprawdza, czy u┼╝ytkownik jest autoryzowany. Wykorzystamy nast─Öpnie stworzony middleware w protectedProcedure - specjalnej, zabezpieczonej procedurze. Ka┼╝da osoba wywo┼éuj─ůca j─ů b─Ödzie musia┼éa spe┼énia─ç warunki autoryzacji - w przeciwnym razie wyst─ůpi b┼é─ůd, kt├│ry w odpowiedni spos├│b b─Ödzie m├│g┼é zosta─ç obs┼éu┼╝ony po stronie klienta.
server/api/trpc.ts
const isAuthed = t.middleware(({ ctx, next }) => {
  if (!ctx.session || !ctx.session.user) {
    throw new TRPCError({ code: "UNAUTHORIZED" });
  }
  return next({
    ctx: {
      // `session` dziedzicz─ůca odpowiedni typ w tym przypadku nie mo┼╝e mie─ç warto┼Ťci `null`
      session: { ...ctx.session, user: ctx.session.user },
    },
  });
});

export const protectedProcedure = t.procedure.use(isAuthed);

Obiekt session to minimalna i lekka reprezentacja u┼╝ytkownika zawieraj─ůca jedynie par─Ö p├│l. Je┼Ťli korzystasz z procedur protectedProcedure, masz dost─Öp do ID u┼╝ytkownika, kt├│re mo┼╝e zosta─ç wykorzystane do pobrania wi─Ökszej ilo┼Ťci danych z bazy.

server/api/routers/user.ts
const userRouter = router({
  me: protectedProcedure.query(async ({ ctx }) => {
    const user = await prisma.user.findUnique({
      where: {
        id: ctx.session.user.id,
      },
    });
    return user;
  }),
});

Korzystanie wraz z Prism─ů

Przygotowanie NextAuth.js do pracy z Prism─ů wymaga wiele wst─Öpnej konfiguracjiÔćŚ. create-t3-app zajmuje si─Ö ni─ů za Ciebie, a je┼Ťli wybierzesz zar├│wno Prism─Ö i NextAuth.js, otrzymasz w pe┼éni dzia┼éaj─ůcy system uwierzytelniania, wraz z wszystkimi potrzebnymi modelami bazy danych. Szablon aplikacji, kt├│ry dostarczamy, zawiera domy┼Ťlnie provider OAuth Discorda, kt├│ry wybrali┼Ťmy z powodu bycia jednym z ┼éatwiejszych do obs┼éugiwania - jedyne, co musiz zrobi─ç, to poda─ç tokeny w pliku .envÔÇŽ i gotowe! Je┼╝eli jednak chcesz skorzysta─ç z czego┼Ť innego, mo┼╝esz w ┼éatwy spos├│b do┼é─ůczy─ç inne providery opieraj─ůc si─Ö na dokumentacji NextAuth.jsÔćŚ. Uwaga - niekt├│re z provider├│w wymagaj─ů wi─Ökszej ilo┼Ťci p├│l na modelach w bazie danych. Polecamy Ci przeczyta─ç dokumentacj─Ö providera, z kt├│rego chcia┼éby┼Ť skorzysta─ç aby upewni─ç si─Ö, i┼╝ posiadasz wszystko, czego potrzebujesz.

Dodawanie nowych p├│l do modeli

Kiedy dodajesz nowe pola do kt├│regokolwiek z modeli - User, Account, Session lub VerificationToken (prawdopodobnie b─Ödziesz chcia┼é zmienia─ç jedynie model User) - pami─Öta─ç musisz, i┼╝ adapter dla PrismyÔćŚ automatycznie tworzy pola na powy┼╝szych modelach w przypadku rejestracji kont i logowania na nie. Z tego te┼╝ powodu, dodaj─ůc nowe pola do tych┼╝e modeli, zaopatrzy─ç musisz je w warto┼Ťci domy┼Ťlne - adapter nie jest ich ┼Ťwiadomy.

Je┼╝eli przyk┼éadowo chcesz doda─ç pole role (roli) do modelu User (u┼╝ytkownika), musisz pami─Öta─ç o do┼é─ůczeniu warto┼Ťci domy┼Ťlnej tego pola. Zrobisz to za pomoc─ů warto┼Ťci @default w modelu User:

prisma/schema.prisma
+ enum Role {
+   USER
+   ADMIN
+ }

  model User {
    ...
+   role Role @default(USER)
  }

Korzystanie wraz z middlewarem Next.js

Wykorzystanie middlewareÔÇÖa Next.js wymaga od Ciebie skorzystania ze strategii JWTÔćŚ do autoryzacji. Dzieje si─Ö tak, poniewa┼╝ middleware jest w stanie pobiera─ç ciasteczko sesji jedynie, gdy jest ono JWT. Create T3 App jest skonfigurowany tak, by wykorzysta─ç domy┼Ťln─ů strategi─Ö bazy danych, wraz z Prism─ů jako jej adapterem.

Konfigurowanie domy┼Ťlnego providera Discord (DiscordProvider)

  1. Przejd┼║ do sekcji Aplikacje w Panelu Discord Developer PortalÔćŚ, a nast─Öpnie kliknij na ÔÇťNew ApplicationÔÇŁ
  2. W menu ustawie┼ä, przejd┼║ do ÔÇťOAuth2 => GeneralÔÇŁ
  • Skopiuj Client ID i wklej go do pliku .env pod kluczem DISCORD_CLIENT_ID.
  • Pod Client Secret, kliknij ÔÇťReset SecretÔÇŁ i skopiuj podany tekst do pliku .env pod kluczem DISCORD_CLIENT_SECRET. Uwa┼╝aj - nie b─Ödziesz m├│g┼é ponownie zobaczy─ç tego klucza, a jego reset spowoduje wyga┼Ťni─Öcie aktualnego.
  • Dodaj ÔÇťAdd RedirectÔÇŁ i wklej tam <app url>/api/auth/callback/discord (przyk┼éadowo dla lokalnej aplikacji: http://localhost:3000/api/auth/callback/discordÔćŚ)
  • Zapisz zmiany
  • Jest mo┼╝liwo┼Ť─ç (nie jest ona jednak polecana), aby wykorzysta─ç t─ů sam─ů aplikacj─Ö Discorda dla zar├│wno aplikacji lokalnej i tej w wersji produkcyjnej. Mo┼╝esz tak┼╝e wykorzysta─ç mockowanie provideraÔćŚ podczas rozwoju aplikacji.

Przydatne Zasoby

Zas├│bLink
Dokumentacja NextAuth.jshttps://next-auth.js.org/ÔćŚ
GitHub NextAuth.jshttps://github.com/nextauthjs/next-authÔćŚ
tRPC Kitchen Sink - wraz z NextAuthhttps://kitchen-sink.trpc.io/next-authÔćŚ

Recent Contributors To This Page