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.

server/auth.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.

server/auth.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. 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 - nie musisz więc importować zarówno opcji NextAuth.js jak i funkcji getServerSession za każdym razem, kiedy chcesz otrzymać sesję.
server/auth.ts
export const getServerAuthSession = (ctx: {
  req: GetServerSidePropsContext["req"];
  res: GetServerSidePropsContext["res"];
}) => {
  return 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
export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
  if (!ctx.session || !ctx.session.user) {
    throw new TRPCError({ code: "UNAUTHORIZED" });
  }
  return next({
    ctx: {
      // inferer `session` som ikke-nullbar
      session: { ...ctx.session, user: ctx.session.user },
    },
  });
}));

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 AUTH_DISCORD_ID.
  • Pod Client Secret, kliknij “Reset Secret” i skopiuj podany tekst do pliku .env pod kluczem AUTH_DISCORD_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