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
- Go to your GitHub Settings
- Developer settings -> OAuth Apps -> New OAuth App
- Fill in the required information
- Set Homepage URL to
http://localhost:9000(or your production URL) - Set Authorization callback URL to
http://localhost:9000/api/auth/github/callback(or your production URL) - Register application
- Generate a Client Secret
- 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.tsimport "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.tsimport 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.tsimport { 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.tsimport { 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.tsimport { Router } from "express";
import OAuthRoutes from "./oauth.routes";
const router = Router();
router.use("/oauth", OAuthRoutes);
export default router;Initialize passport
src/app.tsimport "./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