Today, we will look at setting up authentication in your Qwik City apps with Auth.js. Qwik makes it easy to integrate Auth.js into your app by running the following command:
npm run qwik add auth
This will automatically create a 'plugin@auth.ts' file in the routes directory. You can read the official docs here: https://qwik.builder.io/docs/integrations/authjs/.
Inside the plugin@auth.ts we can pass a provider, for our example we will use google. You can see a list of providers here: https://next-auth.js.org/providers/.
import { serverAuth$ } from "@builder.io/qwik-auth";
import Google from "@auth/core/providers/google";
import type { Provider } from "@auth/core/providers";
import { api } from "../api";
export const { onRequest, useAuthSession, useAuthSignin, useAuthSignout } =
  serverAuth$((req) => ({
    secret: req.env.get("AUTH_SECRET"),
    trustHost: true,
    providers: [
      Google({
        clientId: req.env.get("GOOGLE_CLIENT_ID") as string,
        clientSecret: req.env.get("GOOGLE_CLIENT_SECRET") as string,
        authorization: {
          params: {
            prompt: "consent",
            access_type: "offline",
            response_type: "code",
          },
        },
      }),
    ] as Provider[],
  }));
This 'profile' function is called after a successful authentication and receives the user's profile data and tokens from the provider. You can store the users data in this function for future use.
import { serverAuth$ } from "@builder.io/qwik-auth";
import Google from "@auth/core/providers/google";
import type { Provider } from "@auth/core/providers";
import { api } from "../api";
export const { onRequest, useAuthSession, useAuthSignin, useAuthSignout } =
  serverAuth$((req) => ({
    secret: req.env.get("AUTH_SECRET"),
    trustHost: true,
    providers: [
      Google({
        clientId: req.env.get("GOOGLE_CLIENT_ID") as string,
        clientSecret: req.env.get("GOOGLE_CLIENT_SECRET") as string,
        authorization: {
          params: {
            prompt: "consent",
            access_type: "offline",
            response_type: "code",
          },
        },
        profile: async (profile, tokens) => {
          console.log(profile);
          console.log(tokens);
          //store profile data and/or access_token/refresh_token in the database here
          return {
            id: profile.sub,
            name: profile.name,
            email: profile.email,
            image: profile.picture,
            tokens,
          };
        },
      }),
    ] as Provider[],
  }));
You can use 'useAuthSession' to get the users session data but sometimes you may want to access it in the requestEvent. You can do so with 'sharedMap'.
export const useUserSession = routeLoader$(async (requestEvent) => {
  const appointments = await api(requestEvent).appointments.query.getByEmail({
    email: requestEvent.sharedMap.get("session")?.user?.email,
  });
  console.log(appointments);
  return appointments;
});
You can also access the session cookie and decode the JWT.
import { decode } from "@auth/core/jwt";
export const onRequest: RequestHandler = async ({ cookie, env }) => {
  const sessionToken = cookie.get("next-auth.session-token");
  console.log(sessionToken?.value);
  const decoded = await decode({
    token: sessionToken?.value,
    secret: env.get("AUTH_SECRET") as string,
  });
  console.log(decoded?.email);
};
Protected Routes
export const onRequest: RequestHandler = (event) => {
  const session: Session | null = event.sharedMap.get('session');
  if (!session || new Date(session.expires) < new Date()) {
    throw event.redirect(302, `/api/auth/signin?callbackUrl=${event.url.pathname}`);
  }
};
