Documentation/Build Your First App

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:

User Authentication

Sign in with OAuth42 and display user profile

Session Management

Maintain user sessions with secure tokens

Protected Routes

Restrict access to authenticated users only

Token Refresh

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 installed
    Check with node --version
  • OAuth42 account
    Sign up at app.oauth42.com
  • Basic JavaScript knowledge
    Familiarity with Node.js and Express
  • Code editor
    VS Code, Sublime, or your favorite editor
1

Register Your Application

First, register your application in the OAuth42 dashboard to get your client credentials.

  1. 1.
    Go to app.oauth42.com and sign in
  2. 2.
    Navigate to Applications in the sidebar
  3. 3.
    Click Create Application
  4. 4.
    Fill in the details:
    Name: My First App
    Type: Web Application
    Redirect URIs: http://localhost:3000/callback
    Logout URIs: http://localhost:3000
  5. 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.

2

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 nodemon

Create 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 page

Create 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=3000

Create a .gitignore file:

node_modules/
.env
.DS_Store
*.log
3

Build 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}`);
});
4

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>
5

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 dev

Open your browser and navigate to http://localhost:3000

Testing Flow

  1. 1.
    Click "Sign In with OAuth42"
  2. 2.
    You'll be redirected to OAuth42's login page
  3. 3.
    Enter your credentials and approve the permissions
  4. 4.
    You'll be redirected back to your dashboard
  5. 5.
    Your profile information will be displayed
  6. 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.

6

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.

Why? This prevents authorization code interception attacks. Even if an attacker steals the code, they can't exchange it for tokens without the verifier.

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.

Why? Without state validation, an attacker could initiate a login flow and trick you into completing it, linking their account to your session.

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.

Why? Storing tokens in memory or localStorage exposes them to XSS attacks. Server-side sessions keep tokens secure and inaccessible to JavaScript.

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.

Why? This ensures that sensitive data and functionality are only accessible to authenticated users.

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.

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