Documents
Home>Documents>AI>Agent>Xgen

Building an Auth System with OAuth Social Login

7 min readApr 20, 2025Feb 22, 2026

XGen Authentication System: A Development Log

As XGen expanded from an internal tool to one accessible by external users, a proper authentication system became necessary. This post documents the process of integrating email login alongside Google and GitHub OAuth.

Choosing an Auth Strategy

The first decision was whether to use NextAuth.js (now Auth.js) or roll our own. Since the backend API was already built on FastAPI, we went with a custom JWT-based implementation. On the frontend, the focus was on token management and auth state.

Auth Store Design

// stores/authStore.ts
interface AuthState {
  user: User | null;
  accessToken: string | null;
  isAuthenticated: boolean;
  isLoading: boolean;
  
  login: (email: string, password: string) => Promise<void>;
  socialLogin: (provider: 'google' | 'github') => Promise<void>;
  signup: (data: SignupData) => Promise<void>;
  logout: () => void;
  refreshToken: () => Promise<void>;
}

export const useAuthStore = create<AuthState>()(
  persist(
    (set, get) => ({
      user: null,
      accessToken: null,
      isAuthenticated: false,
      isLoading: false,
      
      login: async (email, password) => {
        set({ isLoading: true });
        try {
          const { data } = await api.post('/auth/login', { email, password });
          set({
            user: data.user,
            accessToken: data.access_token,
            isAuthenticated: true,
          });
        } finally {
          set({ isLoading: false });
        }
      },
      // ...
    }),
    { name: 'auth-storage', partialize: (state) => ({ accessToken: state.accessToken }) }
  )
);

Login Page UI

const LoginPage = () => {
  const { login, socialLogin, isLoading } = useAuthStore();
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  
  return (
    <div className="min-h-screen flex items-center justify-center">
      <div className="w-full max-w-md p-8 bg-white rounded-2xl shadow-lg">
        <h1 className="text-2xl font-bold text-center mb-8">XGen Login</h1>
        
        <form onSubmit={() => login(email, password)}>
          <Input label="이메일" value={email} onChange={setEmail} />
          <Input label="비밀번호" type="password" value={password} onChange={setPassword} />
          <Button type="submit" loading={isLoading}>로그인</Button>
        </form>
        
        <Divider text="또는" />
        
        <div className="space-y-3">
          <SocialButton provider="google" onClick={() => socialLogin('google')}>
            Google로 로그인
          </SocialButton>
          <SocialButton provider="github" onClick={() => socialLogin('github')}>
            GitHub로 로그인
          </SocialButton>
        </div>
      </div>
    </div>
  );
};

Token Management with Axios Interceptors

Axios interceptors handle attaching the token to every outgoing request automatically. On a 401 response, the interceptor attempts a token refresh, and if that fails, it automatically triggers a logout.

Route Protection with Middleware

Next.js middleware guards protected routes by redirecting unauthenticated requests to the login page.

// middleware.ts
export function middleware(request: NextRequest) {
  const token = request.cookies.get('access_token')?.value;
  const protectedPaths = ['/workspace', '/management', '/training'];
  
  if (protectedPaths.some((p) => request.nextUrl.pathname.startsWith(p))) {
    if (!token) {
      return NextResponse.redirect(new URL('/login', request.url));
    }
  }
}

The trickiest part of the OAuth flow was communication between the popup window and the main window. We solved this using the postMessage API.

Tags
AuthenticationOAuthJWTLoginNext.js Middleware