GitHub OAuth (Passport)

The GitHub OAuth component provides a secure and standardized way to integrate GitHub authentication into your Servercn Express applications using the official passport, passport-github2.

It handles the complete OAuth 2.0 flow including authorization URL generation, token exchange, user information retrieval, and token refresh.

Official Docs

Features

  • Complete OAuth 2.0 flow - Authorization, token exchange, and user info retrieval
  • Secure by default - CSRF protection with state parameter
  • Token management - Access token, refresh token, and ID token verification
  • Express integration - Ready-to-use route handlers
  • Type-safe - Full TypeScript support
  • Flexible scopes - Customizable OAuth scopes

Installation Guide

This component requires additional Servercn components.

๐Ÿ‘‰ You do not need to install any Servercn components manually. Running this component installer will automatically install all required components. Manual installation is optional and only recommended if you prefer fine-grained components control

npx servercn-cli add oauth

You will be prompted to select a file upload provider:

? Select OAuth provider:  ยป - Use arrow-keys. Return to submit.
    Google
>   GitHub
    Google + GitHub

The CLI will then automatically configure the component based on your selected provider.

Prerequisites

  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

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"
# replace all with your values

Ensure the following configuration are defined:

MVC: src/configs/env.ts

Feature: src/shared/configs/env.ts

import "dotenv-flow/config";
 
interface Config {
  PORT: number;
  NODE_ENV: string;
  LOG_LEVEL: string;
  CORS_ORIGIN: string;
 
  GITHUB_CLIENT_ID: string;
  GITHUB_CLIENT_SECRET: string;
  GITHUB_REDIRECT_URI: string;
}
 
const env: Config = {
  PORT: Number(process.env.PORT) || 3000,
  NODE_ENV: process.env.NODE_ENV || "development",
  LOG_LEVEL: process.env.LOG_LEVEL || "info",
  CORS_ORIGIN: process.env.CORS_ORIGIN || "*",
 
  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;

Basic Implementation

1. Configure passport in 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);
    }
  )
);

2. Create a GitHub OAuth controller in src/controllers/github-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!"));
    }
 
    // console.log(data);
 
    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
    };
 
    //? save the data into your databases
 
    ApiResponse.ok(res, "Auth Successfull", {
      user
    });
  }
);

3. Create a GitHub OAuth router in src/routes/github-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;

4. Create a server in src/app.ts

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

1. Configure passport in src/shared/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);
    }
  )
);

2. Create a GitHub OAuth controller in src/modules/oauth/github-oauth.controller.ts or src/modules/auth/auth.controller.ts

import { NextFunction, Request, Response } from "express";
import { Profile } from "passport-github2";
 
import { AsyncHandler } from "../../shared/utils/async-handler";
import { ApiError } from "../../shared/errors/api-error";
import { ApiResponse } from "../../shared/utils/api-response";
 
//? 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!"));
    }
 
    // console.log(data);
 
    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
    };
 
    //? save the data into your databases
 
    ApiResponse.ok(res, "Auth Successfull", {
      user
    });
  }
);

3. Create a GitHub OAuth router in src/modules/oauth/github-oauth.routes.ts or src/modules/auth/auth.routes.ts

import { Router } from "express";
import passport from "passport";
import { githubOAuth } from "./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;

4. Create a index route in src/routes/index.ts

import { Router } from "express";
import OAuthRoutes from "../modules/oauth/github-oauth.routes";
 
const router = Router();
 
router.use("/auth", OAuthRoutes);
 
export default router;

5. Create a server in src/app.ts

import express, { Express } from "express";
 
import { notFoundHandler } from "./shared/middlewares/not-found-handler";
import { errorHandler } from "./shared/middlewares/error-handler";
 
import Routes from "./routes/index";
 
import "./shared/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"
    }
  }
}

This response is formated by ApiResponse component.

Common Issues

Ensure your redirect URI in .env exactly matches the one configured in GitHub Developer Settings.

The code passed is incorrect or expired.

  • The authorization code has expired (codes expire after 10 minutes)
  • The code has already been used

File & Folder Structure

ServerCN

Select a file to view its contents

Installation

npx servercn-cli add oauth