Athena

GettingStarted

Get fully setup with the Athena-Auth-UI instance

auth.ts

This is heavily abstracted and then ATHENA_AUTH_UPSTREAM_URL should be the backend athena auth url that the Proxy API forwards to.

ATHENA_AUTH_UPSTREAM_URL=https://auth.athena-cluster.com

lib/auth.ts
import { createAthenaServerAuthClient } from "@xylex-group/athena-auth-ui/athena/client"

export const auth = createAthenaServerAuthClient({
  upstreamUrl: process.env.ATHENA_AUTH_UPSTREAM_URL
})
```ts

Importing the styles into tailwind

```css title="styles/app.css"
@import "tailwindcss";
@import "@heroui/styles";
@import "@xylex-group/athena-auth-ui/styles";

html,
body {
  @apply bg-background text-foreground;
}

Required ENV vars

.env
# Copy values from .env.example and fill with real local secrets.
ATHENA_AUTH_SECRET="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
DATABASE_URL="postgresql://postgres:xxxxxxxxxxxxxxxxxxx@x.x.x.net:31091/xxxxxxxx"
DATABASE_KEY="xxxxxxxxxxxxxxxxxxx"
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
APPLE_CLIENT_ID="com.xxxxxxxxxxxxx"
APPLE_CLIENT_SECRET="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
MICROSOFT_CLIENT_ID="xxxxxxxxxxxxxxxxxxxxxxxxxx"
MICROSOFT_SECRET="xxxxxxxxxxxxxxxxxxx"
GOOGLE_CLIENT_ID="xxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com"
GOOGLE_CLIENT_SECRET="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# Public URL used by @xylex-group/athena auth client in the example app.
NEXT_PUBLIC_ATHENA_AUTH_BASE_URL=/api/auth
# Backend Athena Auth URL that the example API proxy forwards to.
ATHENA_AUTH_UPSTREAM_URL=https://auth.athena-cluster.com

AthenaAuthProvider

Create the provider

components/providers.tsx
"use client";

import { Toast } from "@heroui/react";
import { QueryClientProvider } from "@tanstack/react-query";
import {
  AuthProvider,
  type AuthProviderProps,
} from "@xylex-group/athena-auth-ui";
import { createAthenaAuthClient } from "@xylex-group/athena-auth-ui/athena/client";
import { getAthenaQueryClient } from "@xylex-group/athena-auth-ui/athena/query-client";
import { createAthenaAuthPlugins } from "@xylex-group/athena-auth-ui/plugins";
import { useRouter } from "next/navigation";
import { ThemeProvider, useTheme } from "next-themes";
import type { ReactNode } from "react";

type NavigateArgs = Parameters<NonNullable<AuthProviderProps["navigate"]>>[0];
const authClient = createAthenaAuthClient({
  baseUrl: process.env.NEXT_PUBLIC_ATHENA_AUTH_BASE_URL || "/api/auth",
});

export function Providers({ children }: { children: ReactNode }) {
  const router = useRouter();
  const queryClient = getAthenaQueryClient();

  return (
    <QueryClientProvider client={queryClient}>
      <ThemeProvider
        attribute="data-theme"
        defaultTheme="system"
        disableTransitionOnChange
        enableSystem
      >
        <AuthProvider
          authClient={authClient}
          redirectTo="/settings/account"
          emailAndPassword={{
            confirmPassword: true,
            enabled: true,
            forgotPassword: true,
            requireEmailVerification: true,
          }}
          socialProviders={["google", "github"]}
          navigate={({ to, replace }: NavigateArgs) =>
            replace ? router.replace(to) : router.push(to)
          }
          plugins={createAthenaAuthPlugins({ theme: { useTheme } })}
        >
          {children}

          <Toast.Provider />
        </AuthProvider>
      </ThemeProvider>
    </QueryClientProvider>
  );
}

Wrap the layout with the provider

import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import type { ReactNode } from "react";

import "@/styles/app.css";

import { Header } from "@/components/header";
import { Providers } from "@/components/providers";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Athena Auth UI",
  description: "An initiative of the Athena project",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: ReactNode;
}>) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased min-h-svh flex flex-col`}
      >
        <Providers>
          <Header />

          {children}
        </Providers>
      </body>
    </html>
  );
}

Start out with this one

import { UserButton } from "@xylex-group/athena-auth-ui";

export default function Home() {
  return (
    <div className="flex justify-end">
      <UserButton />
    </div>
  );
}

Setup the Proxy API to callback to the Athena Auth server

import { createAthenaAuthProxyHandlers } from "@xylex-group/athena-auth-ui/athena/proxy";
export const { DELETE, GET, PATCH, POST, PUT } = createAthenaAuthProxyHandlers({
  upstreamUrl: process.env.ATHENA_AUTH_UPSTREAM_URL,
});

Setup the auth pages, (login, password reset etc)

import { AuthPage as AuthRoutePage } from "@xylex-group/athena-auth-ui/pages"

export default async function AuthPage({ params }:
  {
    params: Promise<{ path: string }>
  }
) {
  const { path } = await params
  return <AuthRoutePage path={path} />
}

Setup the settings pages

import { pickAthenaAuthRequestHeaders } from "@xylex-group/athena-auth-ui/athena/request-headers";
import { SettingsPage as SettingsRoutePage } from "@xylex-group/athena-auth-ui/pages";
import { headers } from "next/headers";
import { redirect } from "next/navigation";

import { auth } from "@/lib/auth";

export default async function SettingsPage({
  params,
}: {
  params: Promise<{
    path: string;
  }>;
}) {
  const { path } = await params;

  const requestHeaders = await headers();
  const sessionResponse = await auth.getSession({
    fetchOptions: {
      headers: pickAthenaAuthRequestHeaders(requestHeaders),
    },
  });

  if (!sessionResponse.data?.user) {
    redirect(
      `/auth/sign-in?redirectTo=${encodeURIComponent(`/settings/${path}`)}`,
    );
  }

  return <SettingsRoutePage path={path} />;
}