Skip to main content

OAuth2 Web Integration Example

This guide walks you through our official OAuth2 Web Example — a production-ready Node.js + Express app with session management, JWT decoding, and proper logout handling.

Live Demo

Setup

Clone and run in under 2 minutes:

git clone https://github.com/quran/quran-oauth2-client-example.git
cd quran-oauth2-client-example
cp .env.example .env
npm install
npm start

Edit .env with your credentials:

PORT=3000
BASE_PATH=http://localhost:3000
TOKEN_HOST=https://oauth2.quran.foundation
CLIENT_ID=your_client_id
CLIENT_SECRET=your_client_secret
SCOPES=openid offline_access profile email bookmark collection user
SESSION_SECRET=your_random_session_secret
NODE_ENV=development
Get Your Credentials

The example requires valid OAuth2 credentials. Request access here and register http://localhost:3000/callback as your redirect URI.

Quick Start

  1. Go to http://localhost:3000
  2. Click "Continue with Quran.Foundation"
  3. Log in (or create an account) on Quran.com
  4. Consent to the requested permissions
  5. You'll be redirected back with your user profile displayed!

What You'll Get

After successful authentication, you'll receive:

TokenPurpose
access_tokenUse for API calls (valid ~1 hour)
id_tokenContains user profile info (JWT)
refresh_tokenGet new access tokens without re-login
expires_inSeconds until access token expires

Making API Calls

Use the access_token to call User APIs:

const response = await fetch(
"https://apis.quran.foundation/auth/v1/bookmarks",
{
headers: {
"x-auth-token": access_token,
"x-client-id": process.env.CLIENT_ID,
},
}
);
const bookmarks = await response.json();

Accessing Resources

Once authenticated, explore the User APIs:

  • Collections — Create and manage verse collections
  • Bookmarks — Save and retrieve bookmarked verses
  • Reading Sessions — Track reading progress
  • Goals & Streaks — User reading goals

How the Example Works

Dependencies

DependencyDescription
ExpressMinimal Node.js web framework
simple-oauth2OAuth2 client library
express-sessionSession management
jsonwebtokenJWT decoding for user info
PugTemplating engine

Key Endpoints

EndpointDescription
/Home page (shows login or user profile)
/authRedirects to Quran.Foundation login
/callbackHandles OAuth2 callback, exchanges code for tokens
/logoutClears session and logs out from OAuth2 provider

Code Walkthrough

1. OAuth2 Client Setup

const { AuthorizationCode } = require("simple-oauth2");

const client = new AuthorizationCode({
client: {
id: process.env.CLIENT_ID,
secret: process.env.CLIENT_SECRET,
},
auth: {
tokenHost: process.env.TOKEN_HOST,
tokenPath: "/oauth2/token",
authorizePath: "/oauth2/auth",
},
});

2. Authorization URL with CSRF Protection

const crypto = require("crypto");

app.get("/auth", (req, res) => {
// Generate cryptographically random state for CSRF protection
const state = crypto.randomBytes(32).toString("hex");
req.session.oauthState = state;

const authorizationUri = client.authorizeURL({
redirect_uri: "http://localhost:3000/callback",
scope: process.env.SCOPES,
state,
});
res.redirect(authorizationUri);
});

Parameters:

  • redirect_uri — Where to return after login (must match registered URI exactly)
  • scope — Permissions requested. See available scopes
  • state — CSRF protection (validate on callback)

3. Token Exchange (Callback) with State Validation

app.get("/callback", async (req, res) => {
const { code, state } = req.query;

// Validate state parameter to prevent CSRF attacks
if (!state || state !== req.session.oauthState) {
console.error("State mismatch - possible CSRF attack");
return res.status(403).send("Invalid state parameter");
}

// Clear the stored state after validation
delete req.session.oauthState;

try {
const tokenResponse = await client.getToken({
code,
redirect_uri: "http://localhost:3000/callback",
});

// Store in session
req.session.token = tokenResponse.token;

// Decode ID token for user info
const userInfo = jwt.decode(tokenResponse.token.id_token);
req.session.user = userInfo;

res.redirect("/");
} catch (error) {
console.error("Token exchange failed:", error);
res.status(500).send("Authentication failed");
}
});

4. Logout with Redirect

To redirect users back to your app after logout, include the post_logout_redirect_uri parameter:

app.get("/logout", (req, res) => {
req.session.destroy(() => {
// Build logout URL with redirect back to app
const params = new URLSearchParams({
post_logout_redirect_uri: `${process.env.BASE_PATH}/`,
});

const logoutUrl = `${process.env.TOKEN_HOST}/oauth2/sessions/logout?${params}`;
res.redirect(logoutUrl);
});
});
Pre-Register Your Redirect URI

The post_logout_redirect_uri must be pre-registered in your OAuth2 client's post_logout_redirect_uris configuration. Contact us to update your client settings.

Production Deployment

The example is ready for production deployment. It includes:

  • ✅ Secure session configuration with httpOnly cookies
  • ✅ Proper proxy trust for reverse proxies (Fly.io, etc.)
  • ✅ Environment-based cookie security (secure: true in production)
  • ✅ Logout from both local session and OAuth2 provider

Deploy to Fly.io

fly launch
fly secrets set CLIENT_ID=xxx CLIENT_SECRET=xxx SESSION_SECRET=xxx
fly deploy

Next Steps