Building a Secure App with Next.js & Keycloak Authentication

In this blog post, we'll walk through the step-by-step process of creating a Next.js application with Keycloak as the authentication provider.

We'll use Docker to simplify the Keycloak setup. Let's dive in!

Setting Up Keycloak with Docker

Keycloak is an open-source identity and access management solution. We'll start by running it using Docker.

Create a file named docker-compose.yml with the following content

version: '3.8'

services:
  keycloak:
    image: quay.io/keycloak/keycloak:latest
    container_name: keycloak
    environment:
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin
    command: ["start-dev"]
    ports:
      - "8080:8080"

Run the Container

docker-compose up -d

Access Keycloak: http://localhost:8080

  • Username: admin
  • Password: admin

Configuring Keycloak

  1. Create a Realm: myrealm
  2. Add a Client: my-nextjs-client
    - Client Type: OpenID Connect
    - Redirect URL: http://localhost:3000/api/auth/callback/keycloak
  3. Create a Test User

Setting Up Next.js Blog App

Next, let’s create our Next.js application.

npx create-next-app my-app --ts
cd my-app
yarn add next-auth @next-auth/keycloak

Configure NextAuth for Keycloak

src/app/api/auth/[...nextauth]/route.ts

import NextAuth from "next-auth";
import KeycloakProvider from "next-auth/providers/keycloak";

export default NextAuth({
  providers: [
    KeycloakProvider({
      clientId: process.env.KEYCLOAK_CLIENT_ID!,
      clientSecret: process.env.KEYCLOAK_CLIENT_SECRET!,
      issuer: `${process.env.KEYCLOAK_URL}/realms/${process.env.KEYCLOAK_REALM}`,
      authorization: {
        params: {
          scope: "openid profile email",
        },
      },
    }),
  ],
  callbacks: {
    async jwt({ token, account }) {
      if (account) token.accessToken = account.access_token;
      return token;
    },
    async session({ session, token }) {
      session.accessToken = token.accessToken;
      return session;
    },
  },
  secret: process.env.NEXTAUTH_SECRET,
});

Environment Variables:

# .env.local
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=your_nextauth_secret
KEYCLOAK_URL=http://localhost:8080
KEYCLOAK_REALM=myrealm
KEYCLOAK_CLIENT_ID=my-nextjs-client
KEYCLOAK_CLIENT_SECRET=your_keycloak_client_secret

src/app/components/SessionProvider.tsx

"use client";

import { SessionProvider as NextAuthSessionProvider } from "next-auth/react";
import { ReactNode } from "react";
import { Session } from "next-auth";

interface Props {
  children: ReactNode;
  session?: Session | null;
}

export default function SessionProvider({ children, session }: Props) {
  return (
    <NextAuthSessionProvider session={session}>
      {children}
    </NextAuthSessionProvider>
  );
}

src/app/layout.tsx

import SessionProvider from "@/components/SessionProvider";

export default function RootLayout({
  children,
  session,
}: {
  children: React.ReactNode;
  session?: any;
}) {
  return (
    <html lang="en">
      <body>
        <SessionProvider session={session}>{children}</SessionProvider>
      </body>
    </html>
  );
}

src/app/page.tsx

"use client";

import { useSession, signIn, signOut } from "next-auth/react";
import { useEffect, useState } from "react";

export default function Home() {
  const { data: session } = useSession();
  return (
    <div className="p-6 text-center">
      {session ? (
        <>
          <h1 className="text-xl mb-4">Hello, {session.user?.name}</h1>
          <p>{session.user?.id}</p>
          <p>{session.user?.email}</p>
          <button
            onClick={() => signOut()}
            className="bg-red-500 text-white px-4 py-2 rounded"
          >
            Sign Out
          </button>
        </>
      ) : (
        <button
          onClick={() => signIn("keycloak")}
          className="bg-blue-500 text-white px-4 py-2 rounded"
        >
          Sign In with Keycloak
        </button>
      )}
    </div>
  );
}

Run the Application:

Start Next.js

yarn run dev

Testing Authentication Flow

Visit: http://localhost:3000

  1. Click Login with Keycloak
Page will display login button when you are not signed in

2. Keycloak will authenticate and redirect you to the protected page.

Login with the test user created above

3. You will see the ID and email after login is successful.

Page after login sucessful

Key Insights

  1. Keycloak provides robust identity and access management.
  2. NextAuth integrates seamlessly with Keycloak.
  3. Secure API calls with Bearer tokens.

Congratulations

You’ve built a secure, authenticated using Next.js, Keycloak, and Docker. Happy coding!