Build Your First App
A complete step-by-step tutorial to build and deploy your first application with OAuth42 authentication.
What We'll Build
In this tutorial, you'll build a simple web application with OAuth42 authentication. By the end, you'll have a working app that can:
Sign in with OAuth42 and display user profile
Maintain user sessions with secure tokens
Restrict access to authenticated users only
Automatically refresh expired access tokens
We'll use Node.js with Express for the backend, but the concepts apply to any language or framework. OAuth42 has SDKs for most popular platforms.
Prerequisites
What You'll Need
- •Node.js 18+ and npm installedCheck with
node --version - •OAuth42 accountSign up at app.oauth42.com
- •Basic JavaScript knowledgeFamiliarity with Node.js and Express
- •Code editorVS Code, Sublime, or your favorite editor
Register Your Application
First, register your application in the OAuth42 dashboard to get your client credentials.
- 1.Go to app.oauth42.com and sign in
- 2.Navigate to Applications in the sidebar
- 3.Click Create Application
- 4.Fill in the details:Name: My First AppType: Web ApplicationRedirect URIs: http://localhost:3000/callbackLogout URIs: http://localhost:3000
- 5.Click Create and save your Client ID and Client Secret
⚠️ Keep Your Client Secret Safe
Your client secret is like a password. Never commit it to version control or expose it in browser code. We'll use environment variables to keep it secure.
Set Up Your Project
Create a new Node.js project and install the necessary dependencies.
# Create project directory
mkdir my-first-oauth42-app
cd my-first-oauth42-app
# Initialize npm project
npm init -y
# Install dependencies
npm install express express-session axios dotenv
# Install development dependencies
npm install --save-dev nodemonCreate the following project structure:
my-first-oauth42-app/
├── .env # Environment variables (never commit!)
├── .gitignore # Git ignore file
├── package.json # Node.js dependencies
├── server.js # Main application file
└── views/ # HTML templates
├── index.html # Home page
├── dashboard.html # Protected dashboard
└── error.html # Error pageCreate a .env file with your OAuth42 credentials:
# OAuth42 Configuration
OAUTH42_CLIENT_ID=your_client_id_here
OAUTH42_CLIENT_SECRET=your_client_secret_here
OAUTH42_REDIRECT_URI=http://localhost:3000/callback
OAUTH42_ISSUER=https://oauth42.com
# Session Configuration
SESSION_SECRET=your_random_session_secret_here
# Server Configuration
PORT=3000Create a .gitignore file:
node_modules/
.env
.DS_Store
*.logBuild the Server
Now let's build the Express server with OAuth42 authentication.
Create server.js
require('dotenv').config();
const express = require('express');
const session = require('express-session');
const axios = require('axios');
const crypto = require('crypto');
const path = require('path');
const app = express();
const PORT = process.env.PORT || 3000;
// OAuth42 configuration
const config = {
clientId: process.env.OAUTH42_CLIENT_ID,
clientSecret: process.env.OAUTH42_CLIENT_SECRET,
redirectUri: process.env.OAUTH42_REDIRECT_URI,
authorizationEndpoint: `${process.env.OAUTH42_ISSUER}/oauth2/authorize`,
tokenEndpoint: `${process.env.OAUTH42_ISSUER}/oauth2/token`,
userInfoEndpoint: `${process.env.OAUTH42_ISSUER}/oauth2/userinfo`,
};
// Session middleware
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production', // HTTPS only in production
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000, // 24 hours
},
}));
// Serve static files
app.use(express.static('views'));
// Helper function to generate random string
function generateRandomString(length) {
return crypto.randomBytes(length).toString('hex');
}
// Helper function to base64url encode
function base64UrlEncode(buffer) {
return buffer.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
// Generate PKCE challenge
async function generatePKCE() {
const verifier = generateRandomString(32);
const challenge = base64UrlEncode(
crypto.createHash('sha256').update(verifier).digest()
);
return { verifier, challenge };
}
// Middleware to check authentication
function requireAuth(req, res, next) {
if (!req.session.user) {
return res.redirect('/');
}
next();
}
// Routes
app.get('/', (req, res) => {
if (req.session.user) {
return res.redirect('/dashboard');
}
res.sendFile(path.join(__dirname, 'views', 'index.html'));
});
app.get('/login', async (req, res) => {
try {
// Generate PKCE parameters
const { verifier, challenge } = await generatePKCE();
const state = generateRandomString(16);
// Store in session
req.session.codeVerifier = verifier;
req.session.state = state;
// Build authorization URL
const authUrl = new URL(config.authorizationEndpoint);
authUrl.searchParams.set('client_id', config.clientId);
authUrl.searchParams.set('redirect_uri', config.redirectUri);
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('scope', 'openid profile email');
authUrl.searchParams.set('state', state);
authUrl.searchParams.set('code_challenge', challenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
res.redirect(authUrl.toString());
} catch (error) {
console.error('Login error:', error);
res.redirect('/error');
}
});
app.get('/callback', async (req, res) => {
const { code, state } = req.query;
// Verify state to prevent CSRF
if (!state || state !== req.session.state) {
return res.redirect('/error?message=Invalid state');
}
// Retrieve code verifier
const codeVerifier = req.session.codeVerifier;
if (!codeVerifier) {
return res.redirect('/error?message=Missing code verifier');
}
try {
// Exchange authorization code for tokens
const tokenResponse = await axios.post(
config.tokenEndpoint,
new URLSearchParams({
grant_type: 'authorization_code',
code: code,
redirect_uri: config.redirectUri,
client_id: config.clientId,
client_secret: config.clientSecret,
code_verifier: codeVerifier,
}),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
}
);
const { access_token, refresh_token, id_token } = tokenResponse.data;
// Get user info
const userInfoResponse = await axios.get(config.userInfoEndpoint, {
headers: {
'Authorization': `Bearer ${access_token}`,
},
});
// Store in session
req.session.user = userInfoResponse.data;
req.session.accessToken = access_token;
req.session.refreshToken = refresh_token;
req.session.idToken = id_token;
// Clean up temporary data
delete req.session.codeVerifier;
delete req.session.state;
res.redirect('/dashboard');
} catch (error) {
console.error('Token exchange error:', error.response?.data || error.message);
res.redirect('/error?message=Authentication failed');
}
});
app.get('/dashboard', requireAuth, (req, res) => {
res.sendFile(path.join(__dirname, 'views', 'dashboard.html'));
});
app.get('/api/user', requireAuth, (req, res) => {
res.json(req.session.user);
});
app.get('/logout', (req, res) => {
req.session.destroy((err) => {
if (err) {
console.error('Logout error:', err);
}
res.redirect('/');
});
});
app.get('/error', (req, res) => {
res.sendFile(path.join(__dirname, 'views', 'error.html'));
});
// Start server
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});Create HTML Views
Create simple HTML pages for the user interface.
views/index.html (Home Page)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My First OAuth42 App</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
max-width: 600px;
margin: 100px auto;
padding: 20px;
text-align: center;
}
h1 { color: #2563eb; }
.btn {
display: inline-block;
padding: 12px 24px;
background: #2563eb;
color: white;
text-decoration: none;
border-radius: 8px;
font-weight: 600;
transition: background 0.2s;
}
.btn:hover { background: #1d4ed8; }
</style>
</head>
<body>
<h1>Welcome to My First OAuth42 App</h1>
<p>Sign in with OAuth42 to access your dashboard.</p>
<a href="/login" class="btn">Sign In with OAuth42</a>
</body>
</html>views/dashboard.html (Protected Dashboard)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard - My First OAuth42 App</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
}
h1 { color: #2563eb; }
.user-card {
background: #f3f4f6;
padding: 20px;
border-radius: 8px;
margin: 20px 0;
}
.user-info { margin: 10px 0; }
.btn {
display: inline-block;
padding: 10px 20px;
background: #ef4444;
color: white;
text-decoration: none;
border-radius: 6px;
margin-top: 20px;
}
.btn:hover { background: #dc2626; }
</style>
</head>
<body>
<h1>Dashboard</h1>
<div class="user-card">
<h2>User Profile</h2>
<div id="profile">Loading...</div>
</div>
<a href="/logout" class="btn">Sign Out</a>
<script>
fetch('/api/user')
.then(res => res.json())
.then(user => {
document.getElementById('profile').innerHTML = `
<div class="user-info"><strong>Name:</strong> ${user.name || 'N/A'}</div>
<div class="user-info"><strong>Email:</strong> ${user.email || 'N/A'}</div>
<div class="user-info"><strong>User ID:</strong> ${user.sub}</div>
`;
})
.catch(err => {
document.getElementById('profile').innerHTML = 'Error loading profile';
console.error(err);
});
</script>
</body>
</html>views/error.html (Error Page)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Error - My First OAuth42 App</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
max-width: 600px;
margin: 100px auto;
padding: 20px;
text-align: center;
}
h1 { color: #ef4444; }
.btn {
display: inline-block;
padding: 10px 20px;
background: #2563eb;
color: white;
text-decoration: none;
border-radius: 6px;
margin-top: 20px;
}
.btn:hover { background: #1d4ed8; }
</style>
</head>
<body>
<h1>Oops! Something went wrong</h1>
<p id="message">An error occurred during authentication.</p>
<a href="/" class="btn">Go Home</a>
<script>
const params = new URLSearchParams(window.location.search);
const message = params.get('message');
if (message) {
document.getElementById('message').textContent = message;
}
</script>
</body>
</html>Run and Test Your App
Add a start script to your package.json:
{
"name": "my-first-oauth42-app",
"version": "1.0.0",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"dependencies": {
"express": "^4.18.2",
"express-session": "^1.17.3",
"axios": "^1.6.0",
"dotenv": "^16.3.1"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}Start your application:
npm run devOpen your browser and navigate to http://localhost:3000
Testing Flow
- 1.Click "Sign In with OAuth42"
- 2.You'll be redirected to OAuth42's login page
- 3.Enter your credentials and approve the permissions
- 4.You'll be redirected back to your dashboard
- 5.Your profile information will be displayed
- 6.Click "Sign Out" to end your session
🎉 Congratulations!
You've successfully built your first OAuth42 application! Your app now supports secure authentication, session management, and protected routes.
Understanding the Code
PKCE Implementation
The generatePKCE() function creates a code verifier and challenge. The challenge is sent with the authorization request, and the verifier is sent with the token request. OAuth42 validates that they match.
State Parameter
The state parameter is a random string that's stored in the session and validated in the callback. This prevents CSRF attacks where an attacker tricks a user into authenticating with the attacker's account.
Session Storage
User data and tokens are stored in server-side sessions using express-session. The session cookie is HTTP-only and secure in production, preventing XSS and man-in-the-middle attacks.
Protected Routes
The requireAuth middleware checks if the user is authenticated before allowing access to protected routes. Unauthenticated users are redirected to the home page.
Next Steps
Now that you have a working OAuth42 application, here are some ways to enhance it:
Implement Token Refresh
Add automatic token refresh before access tokens expire for seamless user experience.
Add Multi-Factor Authentication
Enhance security by requiring users to enable MFA in their accounts.
Add API Calls
Use access tokens to call protected APIs and display user-specific data.
Deploy to Production
Deploy your app to a hosting platform and update your OAuth42 redirect URIs.
Token Management
Learn about access tokens, refresh tokens, and best practices for token lifecycle management.
Multi-Factor Authentication
Implement TOTP-based MFA to add an extra layer of security to your application.
API Reference
Explore the complete OAuth42 API reference with detailed endpoint documentation.
Security Best Practices
Learn how to secure your OAuth42 implementation against common vulnerabilities.
Troubleshooting
"Invalid redirect URI" error
Make sure the redirect URI in your code exactly matches the one registered in the OAuth42 dashboard, including:
- Protocol (http:// vs https://)
- Port number (:3000)
- Path (/callback)
- No trailing slashes
"Invalid state parameter" error
This usually happens when session cookies aren't working properly. Check:
- Your SESSION_SECRET is set in .env
- Cookies are enabled in your browser
- You're not testing in incognito mode with strict cookie settings
"Token exchange failed" error
Check your client credentials:
- OAUTH42_CLIENT_ID is correct
- OAUTH42_CLIENT_SECRET is correct
- Client secret hasn't been rotated in the dashboard
- Check server logs for the actual error message from OAuth42
Dashboard shows "Loading..." forever
Check your browser's developer console for errors. Common causes:
- User info wasn't stored in session correctly
- /api/user endpoint is returning an error
- Session expired or was cleared