What this covers

A practical GitHub OAuth flow using Passport, designed for stateless APIs.
This focuses on configuration, routing, and controller logic — not theory.

Stack

  • Node.js
  • Express
  • TypeScript
  • passport
  • passport-github2

GitHub OAuth Setup

  1. Go to your GitHub Settings
  2. Developer settings -> OAuth Apps -> New OAuth App
  3. Fill in the required information
  4. Set Homepage URL to http://localhost:9000 (or your production URL)
  5. Set Authorization callback URL to http://localhost:9000/api/auth/github/callback (or your production URL)
  6. Register application
  7. Generate a Client Secret
  8. Copy the Client ID and Client Secret

Implementation

Install packages

  • Dependencies

npm install passport passport-github2 dotenv
  • TypeScript types

npm install -D @types/passport @types/passport-github2

Setup Environment variables

Add the following to your .env file:

GITHUB_CLIENT_ID="your-github-client-id"
GITHUB_CLIENT_SECRET="your-github-client-secret"
GITHUB_REDIRECT_URI="your-github-redirectUri"

Setup Environment Configuration

src/configs/env.ts
import "dotenv/config";
 
interface Config {
  PORT: number;
  NODE_ENV: string;
  LOG_LEVEL: string;
 
  GITHUB_CLIENT_ID: string;
  GITHUB_CLIENT_SECRET: string;
  GITHUB_REDIRECT_URI: string;
}
 
const env: Config = {
  PORT: Number(process.env.PORT) || 1111,
  NODE_ENV: process.env.NODE_ENV || "development",
  LOG_LEVEL: process.env.LOG_LEVEL || "info",
 
  GITHUB_CLIENT_ID: process.env.GITHUB_CLIENT_ID!,
  GITHUB_CLIENT_SECRET: process.env.GITHUB_CLIENT_SECRET!,
  GITHUB_REDIRECT_URI: process.env.GITHUB_REDIRECT_URI!
};
 
export default env;

Configure Passport with GitHub Strategy

src/configs/passport.ts
import passport from "passport";
import { Strategy as GitHubStrategy, Profile } from "passport-github2";
import env from "./env";
 
passport.use(
  new GitHubStrategy(
    {
      clientID: env.GITHUB_CLIENT_ID,
      clientSecret: env.GITHUB_CLIENT_SECRET,
      callbackURL: env.GITHUB_REDIRECT_URI
    },
    function (
      accessToken: string,
      refreshToken: string,
      profile: Profile,
      cb: (error: Error | null, user?: any) => void
    ) {
      // console.log({ profile });
      return cb(null, profile);
    }
  )
);

Configure OAuth controller

src/controllers/oauth.controller.ts or

src/controllers/auth.controller.ts
import { NextFunction, Request, Response } from "express";
import { Profile } from "passport-github2";
 
import { ApiResponse } from "../utils/api-response";
import { AsyncHandler } from "../utils/async-handler";
import { ApiError } from "../utils/api-error";
 
//? login with github
export const githubOAuth = AsyncHandler(
  async (req: Request, res: Response, next: NextFunction) => {
    const data = req.user as Profile | undefined;
 
    if (!data) {
      return next(ApiError.unauthorized("Authenticated failed!"));
    }
 
    const user = {
      provider: data?.provider,
      providerId: data.id,
      name: data.displayName,
      email: data?.emails && data?.emails[0]?.value,
      isEmailVerified: true,
      avatar: data.photos && data.photos[0].value
    };
 
    // Persist user here (DB)
 
    ApiResponse.ok(res, "Auth Successfull", {
      user
    });
  }
);

Configure OAuth routes

src/routes/oauth.routes.ts or

src/routes/auth.routes.ts
import { Router } from "express";
import passport from "passport";
import { githubOAuth } from "../controllers/github-oauth.controller";
 
const router = Router();
 
router.get(
  "/github",
  passport.authenticate("github", { scope: ["user:email"] })
);
 
router.get(
  "/github/callback",
  passport.authenticate("github", {
    failureRedirect: "/login", //? redirect route if authenticated is failed,
    session: false
  }),
  githubOAuth
);
 
export default router;

Mount routes

src/routes/index.ts
import { Router } from "express";
import OAuthRoutes from "./oauth.routes";
 
const router = Router();
 
router.use("/oauth", OAuthRoutes);
 
export default router;

Initialize passport

src/app.ts
import "./configs/passport";
import express, { Express, Request, Response } from "express";
 
import { notFoundHandler } from "./middlewares/not-found-handler";
import { errorHandler } from "./middlewares/error-handler";
 
import Routes from "./routes/index";
 
import "./configs/passport";
 
const app: Express = express();
 
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
 
// Routes
app.use("/api", Routes);
 
// Not found handler (should be after routes)
app.use(notFoundHandler);
 
// Global error handler (should be last)
app.use(errorHandler);
 
export default app;

Success Response

{
  "success": true,
  "message": "Auth Successfull",
  "statusCode": 200,
  "data": {
    "user": {
      "provider": "github",
      "providerId": "170957638",
      "name": "Akkal Dhami",
      "email": "dhamiakkal21@gmail.com",
      "isEmailVerified": true,
      "avatar": "https://avatars.githubusercontent.com/u/170957638?v=4"
    }
  }
}

CLI

npx servercn-cli add oauth

Akkal

Dhami