Adding Authentication to a Qwik City App
auth icon

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}`);
  }
};