Overview
Diffy API uses a two-step authentication process:
GitHub OAuth - Users authenticate via GitHub to link their account
JWT Tokens - API requests are authenticated using JWT bearer tokens
GitHub OAuth Flow
Initiate OAuth Flow
Redirect users to the GitHub OAuth endpoint to start authentication: window . location . href = 'https://api.diffy.dev/auth/github' ;
This redirects to GitHub’s authorization page where users grant access to your application.
Handle OAuth Callback
After successful authentication, GitHub redirects back to your callback URL with a JWT token: // Parse token from URL
const urlParams = new URLSearchParams ( window . location . search );
const token = urlParams . get ( 'token' );
// Store token for API requests
localStorage . setItem ( 'diffy_token' , token );
Use Token for API Requests
Include the JWT token in the Authorization header for all API requests: const response = await fetch ( 'https://api.diffy.dev/pull-requests' , {
headers: {
'Authorization' : `Bearer ${ token } `
}
});
Authentication Implementation
Backend OAuth Controller
The authentication flow is handled by the auth controller:
auth.controller.ts
auth.service.ts
import { Controller , Get , Redirect , UseGuards , Req , Res } from '@nestjs/common' ;
import { AuthService } from './auth.service' ;
import { AuthGuard } from '@nestjs/passport' ;
import type { Request , Response } from 'express' ;
import { ConfigService } from '@nestjs/config' ;
@ Controller ( 'auth' )
export class AuthController {
constructor (
private readonly authService : AuthService ,
private readonly configService : ConfigService ,
) {}
@ Get ( 'github' )
@ UseGuards ( AuthGuard ( 'github2' ))
@ Redirect (
`https://github.com/login/oauth/authorize?client_id= ${ process . env . GITHUB_CLIENT_ID } ` ,
302 ,
)
async GitHub () {}
@ Get ( 'callback' )
@ UseGuards ( AuthGuard ( 'github2' ))
GitHubCallback (@ Res () res : Response , @ Req () req : Request ) {
const token = this . authService . generateToken ( req . user as JwtPayload );
return res . redirect (
` ${ this . configService . get < string >( 'GITHUB_REDIRECT_URL' ) } ?token= ${ token } ` ,
);
}
}
GitHub Strategy
The GitHub OAuth strategy validates users and creates accounts:
import { Profile , Strategy } from 'passport-github2' ;
import { PassportStrategy } from '@nestjs/passport' ;
import { Injectable } from '@nestjs/common' ;
import { AuthService } from '../auth.service' ;
import { ConfigService } from '@nestjs/config' ;
@ Injectable ()
export class GithubStrategy extends PassportStrategy ( Strategy , 'github2' ) {
constructor (
private authService : AuthService ,
private configService : ConfigService ,
) {
super ({
clientID: configService . get < string >( 'GITHUB_APP_CLIENT_ID' ) ! ,
clientSecret: configService . get < string >( 'GITHUB_APP_CLIENT_SECRET' ) ! ,
callbackURL: configService . get < string >( 'GITHUB_APP_CALLBACK_URL' ) ! ,
scope: [ 'user:email' ],
});
}
async validate (
accessToken : string ,
refreshToken : string ,
profile : Profile ,
done : ( err : Error | null , user ?: any ) => void ,
) : Promise < any > {
const { id , displayName , username , emails , photos } = profile ;
const userDetails = {
githubId: id ,
username: username ,
email: emails ? emails [ 0 ]. value : undefined ,
avatarUrl: photos ? photos [ 0 ]. value : undefined ,
name: displayName ,
githubAccessToken: accessToken ,
githubRefreshToken: refreshToken ,
};
try {
const user = await this . authService . validateOrCreateUser ( userDetails );
done ( null , user );
} catch ( err ) {
done ( err as Error , null );
}
}
}
JWT Strategy
The JWT strategy validates tokens on protected routes:
import { ExtractJwt , Strategy } from 'passport-jwt' ;
import { PassportStrategy } from '@nestjs/passport' ;
import { Injectable } from '@nestjs/common' ;
import { ConfigService } from '@nestjs/config' ;
import { JwtPayload } from '../interfaces/jwt-payload.interface' ;
@ Injectable ()
export class JwtStrategy extends PassportStrategy ( Strategy ) {
constructor ( private configService : ConfigService ) {
super ({
jwtFromRequest: ExtractJwt . fromAuthHeaderAsBearerToken (),
ignoreExpiration: false ,
secretOrKey: configService . get < string >( 'JWT_SECRET' ) ! ,
});
}
validate ( payload : JwtPayload ) {
return { id: payload . id , githubId: payload . githubId , name: payload . name };
}
}
Protecting Routes
Use the JwtAuthGuard to protect API endpoints:
import { Controller , Get , UseGuards } from '@nestjs/common' ;
import { JwtAuthGuard } from 'src/auth/guards/jwt.guard' ;
@ Controller ( 'pull-requests' )
export class PullRequestsController {
@ Get ()
@ UseGuards ( JwtAuthGuard )
async getPullRequests () {
// Only authenticated users can access this
return [];
}
}
Environment Variables
Configure these environment variables for authentication:
# GitHub OAuth App Credentials
GITHUB_APP_CLIENT_ID = your_github_client_id
GITHUB_APP_CLIENT_SECRET = your_github_client_secret
GITHUB_APP_CALLBACK_URL = http://localhost:3000/auth/callback
# Redirect URL after authentication
GITHUB_REDIRECT_URL = http://localhost:5173
# JWT Configuration
JWT_SECRET = your_jwt_secret_key_here
Keep your JWT_SECRET and GITHUB_APP_CLIENT_SECRET secure. Never commit these to version control.
JWT Token Payload
The JWT token contains the following user information:
interface JwtPayload {
id : number ; // User's database ID
githubId : string ; // GitHub user ID
name : string ; // User's display name
}
Token Expiration
JWT tokens expire after 1 hour . Clients should handle token expiration gracefully:
const response = await fetch ( 'https://api.diffy.dev/pull-requests' , {
headers: {
'Authorization' : `Bearer ${ token } `
}
});
if ( response . status === 401 ) {
// Token expired - redirect to login
window . location . href = '/login' ;
}
Tokens are configured with a 1-hour expiration in the JwtModule configuration (see auth.module.ts:19-21).
Frontend Integration Example
Here’s a complete example of integrating authentication in a React application:
import { useState , useEffect } from 'react' ;
interface User {
id : number ;
githubId : string ;
name : string ;
}
export function useAuth () {
const [ user , setUser ] = useState < User | null >( null );
const [ token , setToken ] = useState < string | null >( null );
useEffect (() => {
// Check for token in URL (from OAuth callback)
const urlParams = new URLSearchParams ( window . location . search );
const tokenFromUrl = urlParams . get ( 'token' );
if ( tokenFromUrl ) {
localStorage . setItem ( 'diffy_token' , tokenFromUrl );
setToken ( tokenFromUrl );
// Clean up URL
window . history . replaceState ({}, '' , window . location . pathname );
} else {
// Check localStorage
const storedToken = localStorage . getItem ( 'diffy_token' );
if ( storedToken ) {
setToken ( storedToken );
}
}
}, []);
const login = () => {
window . location . href = 'https://api.diffy.dev/auth/github' ;
};
const logout = () => {
localStorage . removeItem ( 'diffy_token' );
setToken ( null );
setUser ( null );
};
return { user , token , login , logout };
}
Testing Authentication
Test the authentication flow using curl:
# 1. Get a token (replace with actual token from OAuth flow)
TOKEN = "your_jwt_token_here"
# 2. Test authenticated endpoint
curl -H "Authorization: Bearer $TOKEN " \
https://api.diffy.dev/github/pull-request/owner/repo/1