IDP Support Center
  • Welcome
  • Getting started
    • What is IDP?
  • User Guide
    • User guide center
    • Get started with OXU
      • OXU Identity user guide
    • Account Management
      • Authentication
        • Sign up with Email and Password
        • Sign up with Google (optional)
        • Sign in with Email and Password
        • Google OIDC: Sign in to Google via OXU
        • Password requirements enforcement
        • Secure password hashing and storage
      • Account security
        • What should I do if I don't receive a verification code when signing up or forgot password?
        • Email verification
        • Two-Factor Authentication
          • Passkey authentication
          • 2FA-Email verification codes
          • MFA-Authenticator apps (TOTP)
          • MFA-Passkeys (FIDO2 / WebAuthn)
      • Data & Privacy
      • User Experience
        • Cross-platform web support
      • App Consent Management
    • Organization Admin App
      • Business Owner (Default Authority)
        • Organization Management
          • Organization creation
          • Domain ownership verification (DNS)
        • Organizational Structure
          • Organizational Units (OUs)
          • Workspaces
          • Hierarchical access scoping
        • Security & Governance
          • Enforced security policies
            • IP-Based access control
            • Geo-based access policies
            • Device and platform restrictions
          • Enforcing stronger authentication for risky login behavior
            • Risk Detection signals
            • Step-Up authentication
        • Roles & permissions
          • Assign role to user in Organization
          • Permissions list
      • SCIM – Automated user and workspace provisioning
        • What is SCIM used for?
        • User Lifecycle management
      • Administrator
        • Team & Access
          • Invite and manage users
          • Assign roles
          • Manage access at OU and Workspace level
            • Manage Access at Organizational Unit (OU)
            • Manage Workspace level
              • Team & Access – Members Management
        • Group management
        • Organization Unit
          • Create & manage Organization Units
          • Viewing and searching Organizational Units
          • Moving an Organizational Unit
          • Deleting an Organizational Unit
        • Manage activity logs of Organization's member
      • Become an OXU developer
      • App management - Workspace access
      • Report & Analytics Center
    • OXU Workspace
      • Guide to create workspace
      • Guide to manage workspace information
      • Guide to manage role and permissions
      • Applications
        • Guide to manage applications
        • OXU Developer
          • What is OXU developer
          • User guides
            • 1. Become OXU developer
            • 2. Create an app
            • 3. Input application info
              • About app ratings and reviews
            • 4. Config resource & security info
            • Security Best Practices
            • 5. Set up Pricing info
            • 6. Publish your app
              • Prepare before publishing your app
            • 7. Manage your app
              • App lifecycle
          • App versioning
        • OXU Store
          • What is OXU Store
          • 1. Register as an user
          • 2. Browsing & searching apps
          • 3. View app details
          • 4. Subscribe an app
            • Enable & Subscribe app for business workspace
          • 5. Manage subscriptions
          • 6. Rate & review an app
  • Support
    • Support center
      • What is Oten account & what can I do with Account Management App?
      • How to create account and password?
      • How to manage your Oten account information?
      • How to use MFA to protect your account?
      • What is WorkSpace & what can you do with WS?
      • What is Organization Admin app & what can you do with OAA?
    • Privacy Policy
    • Terms and conditions
      • Oten developer terms and conditions
      • Oten Store terms and conditions
    • FAQs
      • Store FAQs
      • Developer FAQ
    • Contact Us
  • Integration
    • Integration document
      • IDP integration
        • Environments: sandbox & production
        • Regular web application client
        • Native application client
        • Single page application client
        • SAML integration
        • Managing your integration applications
        • FAQ
      • What is SSO?
      • Why use SSO?
    • Provisioning connector
      • Google Workspace Configuration
    • Understand SSO flow
      • Overview
      • Flow Diagram
    • Developer Integration guide
      • Integration flow overview
    • Oten to OXU Migration guide
    • Prerequisites
      • Discovery Configuration
      • JAR Requirement - CRITICAL
      • JAR Complete Implementation Guide
      • PKCE Implementation Guide
        • Step 1: Choose OAuth Library
        • Step 2: Configure OAuth Client
        • Step 3: Implement Authorization Flow
        • Step 4: Handle Callback
        • Step 5: Token Management
      • Best practice
        • Security
      • Support & Troubleshoot
        • Common Errors
        • Debug and Troubleshooting
        • Contact Support
      • Appendix
        • Configuration Reference
        • Error Codes Reference
        • API Reference
        • Sample Code
        • Glossary
  • What's New
    • v1.0.29 - Aug 19, 2026
    • v1.0.28 - Aug 12, 2026
    • v1.0.27 - Aug 08, 2026
    • v1.0.26 - July 29 & 31, 2026
    • v1.0.25 - July 22, 2026
    • v1.0.24 - Jun 21, 2026
    • v1.0.23 - Jun 17, 2026
    • v1.0.22 - Jun 03, 2026
    • v1.0.21 - May 27, 2026
    • v1.0.20 - Apr 28, 2026
    • v1.0.19 - Apr 21, 2026
    • v1.0.18 - Apr 15, 2026
    • v1.0.17 - Apr 03, 2026
    • v1.0.16 - Mar 28, 2026
    • v1.0.15 - Mar 05 & 13, 2026
    • v1.0.14 - Feb 11, 2026
    • v1.0.13 - Jan 14, 2026
    • v1.0.12 - Jan 05, 2026
    • v1.0.11 - Jan 04, 2026
    • v1.0.10 - Dec 25, 2025
    • v1.0.9 - Dec 07, 2025
    • v1.0.8 - Nov 23, 2025
    • v1.0.7 - Nov 09, 2025
    • v1.0.6 - Oct 26, 2025
    • v1.0.5 - Sep 29, 2025
    • v1.0.4 - Sep 28, 2025
    • v1.0.3 - Sep 14, 2025
    • v1.0.2 - Aug 31, 2025
    • v1.0.1 - Aug 17, 2025
    • v1.0.0 - Aug 03, 2025
On this page
  1. Integration
  2. Prerequisites
  3. Best practice

Security

PreviousBest practiceNextSupport & Troubleshoot

Last updated 10 months ago

  • Security Fundamentals
  • Core Principles
  • Transport Security
  • Always Use HTTPS
  • Certificate Validation
  • 🛡️ CSRF Protection
  • State Parameter Implementation
  • 🔐 PKCE for Public Clients
  • When to Use PKCE
  • PKCE Implementation
  • 🎫 Token Security
  • Secure Token Storage
  • Token Validation
  • 🔍 Input Validation
  • Validate All OAuth Parameters
  • Sanitize User Data
  • Error Handling Security
  • Don't Leak Sensitive Information
  • 🔐 Session Security
  • Secure Session Configuration
  • Content Security Policy
  • CSP for OAuth Applications
  • Security Monitoring
  • Monitor Authentication Events
  • Rate Limiting

Security is paramount when implementing SSO. This guide covers essential security practices to protect your users and applications.

Security Fundamentals

Core Principles

  1. Defense in Depth: Multiple layers of security

  2. Least Privilege: Grant minimum necessary access

  3. Zero Trust: Verify everything, trust nothing

  4. Secure by Default: Safe configurations out of the box

Transport Security

Always Use HTTPS

✅ Correct Implementation

// Production configuration
const redirectURI = "https://myapp.com/callback";
const authURL = "https://account.oten.com/v1/oauth/authorize";

// Enforce HTTPS in your application
app.use((req, res, next) => {

  • Single Page Applications (SPAs)

  • Mobile applications

  • Any client that cannot securely store secrets


Next: Learn about Token Management best practices

❌ Wrong Implementation

// NEVER use HTTP in production
const redirectURI = "http://myapp.com/callback"; // Vulnerable!

Certificate Validation

// Verify SSL certificates
const https = require('https');

const agent = new https.Agent({
    rejectUnauthorized: true, // Always verify certificates
    checkServerIdentity: (host, cert) => {
        // Additional certificate validation if needed
        return undefined; // No error = valid
    }
});

🛡️ CSRF Protection

State Parameter Implementation

Generate Secure State

const crypto = require('crypto');

function generateState() {
    // Generate cryptographically secure random state
    return crypto.randomBytes(32).toString('hex');
}

function createAuthURL() {
    const state = generateState();
    
    // Store state in session for later verification
    req.session.oauthState = state;
    
    const authURL = buildAuthorizationURL({
        client_id: clientId,
        redirect_uri: redirectURI,
        response_type: 'code',
        scope: 'openid profile email',
        state: state
    });
    
    return authURL;
}

Validate State

function validateCallback(req, res) {
    const receivedState = req.query.state;
    const storedState = req.session.oauthState;
    
    // Clear stored state
    delete req.session.oauthState;
    
    if (!receivedState || receivedState !== storedState) {
        throw new Error('Invalid state parameter - possible CSRF attack');
    }
    
    // Continue with token exchange...
}

🔐 PKCE for Public Clients

When to Use PKCE

PKCE Implementation

const crypto = require('crypto');

function generatePKCE() {
    // Generate code verifier (43-128 characters)
    const codeVerifier = crypto.randomBytes(96).toString('base64url');
    
    // Generate code challenge
    const codeChallenge = crypto
        .createHash('sha256')
        .update(codeVerifier)
        .digest('base64url');
    
    return {
        codeVerifier,
        codeChallenge,
        codeChallengeMethod: 'S256'
    };
}

// In authorization flow
const pkce = generatePKCE();

// Store code verifier securely (session storage for SPAs)
sessionStorage.setItem('code_verifier', pkce.codeVerifier);

// Include in authorization URL
const authURL = buildAuthorizationURL({
    // ... other parameters
    code_challenge: pkce.codeChallenge,
    code_challenge_method: pkce.codeChallengeMethod
});

// In token exchange
const tokenRequest = {
    grant_type: 'authorization_code',
    code: authorizationCode,
    redirect_uri: redirectURI,
    client_id: clientId,
    code_verifier: sessionStorage.getItem('code_verifier')
    // Note: No client_secret for public clients
};

🎫 Token Security

Secure Token Storage

Server-Side Applications

// Store tokens encrypted in database
const crypto = require('crypto');

function encryptToken(token, secretKey) {
    const cipher = crypto.createCipher('aes-256-cbc', secretKey);
    let encrypted = cipher.update(token, 'utf8', 'hex');
    encrypted += cipher.final('hex');
    return encrypted;
}

function decryptToken(encryptedToken, secretKey) {
    const decipher = crypto.createDecipher('aes-256-cbc', secretKey);
    let decrypted = decipher.update(encryptedToken, 'hex', 'utf8');
    decrypted += decipher.final('utf8');
    return decrypted;
}

// Save user tokens
async function saveUserTokens(userId, tokens) {
    const encryptedTokens = {
        accessToken: encryptToken(tokens.access_token, process.env.TOKEN_ENCRYPTION_KEY),
        refreshToken: encryptToken(tokens.refresh_token, process.env.TOKEN_ENCRYPTION_KEY),
        expiresAt: new Date(Date.now() + tokens.expires_in * 1000)
    };
    
    await database.saveUserTokens(userId, encryptedTokens);
}

Client-Side Applications (SPAs)

// Use secure storage mechanisms
class SecureTokenStorage {
    constructor() {
        this.storage = window.sessionStorage; // More secure than localStorage
    }
    
    setTokens(tokens) {
        // Store with expiration
        const tokenData = {
            ...tokens,
            storedAt: Date.now()
        };
        
        this.storage.setItem('auth_tokens', JSON.stringify(tokenData));
    }
    
    getTokens() {
        const stored = this.storage.getItem('auth_tokens');
        if (!stored) return null;
        
        const tokenData = JSON.parse(stored);
        
        // Check if tokens are expired
        const now = Date.now();
        const expiresAt = tokenData.storedAt + (tokenData.expires_in * 1000);
        
        if (now >= expiresAt) {
            this.clearTokens();
            return null;
        }
        
        return tokenData;
    }
    
    clearTokens() {
        this.storage.removeItem('auth_tokens');
    }
}

Token Validation

ID Token Validation

const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');

// Create JWKS client
const client = jwksClient({
    jwksUri: 'https://account.oten.com/.well-known/jwks.json',
    cache: true,
    cacheMaxAge: 86400000, // 24 hours
    rateLimit: true,
    jwksRequestsPerMinute: 5
});

function getKey(header, callback) {
    client.getSigningKey(header.kid, (err, key) => {
        if (err) {
            callback(err);
            return;
        }
        
        const signingKey = key.publicKey || key.rsaPublicKey;
        callback(null, signingKey);
    });
}

async function validateIDToken(idToken) {
    return new Promise((resolve, reject) => {
        jwt.verify(idToken, getKey, {
            issuer: 'https://account.oten.com',
            audience: process.env.CLIENT_ID,
            algorithms: ['RS256']
        }, (err, decoded) => {
            if (err) {
                reject(new Error(`Invalid ID token: ${err.message}`));
                return;
            }
            
            // Additional validations
            const now = Math.floor(Date.now() / 1000);
            
            if (decoded.exp <= now) {
                reject(new Error('ID token has expired'));
                return;
            }
            
            if (decoded.iat > now + 300) { // Allow 5 minutes clock skew
                reject(new Error('ID token issued in the future'));
                return;
            }
            
            resolve(decoded);
        });
    });
}

🔍 Input Validation

Validate All OAuth Parameters

function validateOAuthCallback(req) {
    const { code, state, error, error_description } = req.query;
    
    // Check for OAuth errors
    if (error) {
        const errorMsg = error_description || error;
        throw new Error(`OAuth error: ${errorMsg}`);
    }
    
    // Validate authorization code
    if (!code || typeof code !== 'string') {
        throw new Error('Missing or invalid authorization code');
    }
    
    if (code.length < 10 || code.length > 512) {
        throw new Error('Authorization code length invalid');
    }
    
    // Validate state parameter
    if (!state || typeof state !== 'string') {
        throw new Error('Missing or invalid state parameter');
    }
    
    if (!/^[a-zA-Z0-9_-]+$/.test(state)) {
        throw new Error('State parameter contains invalid characters');
    }
    
    return { code, state };
}

Sanitize User Data

function sanitizeUserClaims(claims) {
    const allowedClaims = [
        'sub', 'email', 'name', 'given_name', 
        'family_name', 'picture', 'locale'
    ];
    
    const sanitized = {};
    
    for (const claim of allowedClaims) {
        if (claims[claim] && typeof claims[claim] === 'string') {
            // Basic sanitization
            sanitized[claim] = claims[claim]
                .trim()
                .substring(0, 255) // Limit length
                .replace(/[<>]/g, ''); // Remove potential XSS characters
        }
    }
    
    return sanitized;
}

Error Handling Security

Don't Leak Sensitive Information

function handleAuthError(error, req, res) {
    // Log detailed error for debugging
    console.error('Auth error details:', {
        error: error.message,
        stack: error.stack,
        userAgent: req.headers['user-agent'],
        ip: req.ip,
        timestamp: new Date().toISOString()
    });
    
    // Return generic error to user
    let userMessage = 'Authentication failed. Please try again.';
    let statusCode = 400;
    
    // Map specific errors to user-friendly messages
    if (error.message.includes('access_denied')) {
        userMessage = 'Access was denied. Please contact your administrator.';
    } else if (error.message.includes('invalid_grant')) {
        userMessage = 'Your session has expired. Please log in again.';
        statusCode = 401;
    } else if (error.message.includes('server_error')) {
        userMessage = 'Service temporarily unavailable. Please try again later.';
        statusCode = 503;
    }
    
    res.status(statusCode).json({
        error: 'authentication_failed',
        message: userMessage
    });
}

🔐 Session Security

Secure Session Configuration

const session = require('express-session');
const MongoStore = require('connect-mongo');

app.use(session({
    secret: process.env.SESSION_SECRET, // Strong, random secret
    name: 'sessionId', // Don't use default name
    resave: false,
    saveUninitialized: false,
    rolling: true, // Reset expiration on activity
    cookie: {
        secure: process.env.NODE_ENV === 'production', // HTTPS only in production
        httpOnly: true, // Prevent XSS
        maxAge: 30 * 60 * 1000, // 30 minutes
        sameSite: 'lax' // CSRF protection
    },
    store: MongoStore.create({
        mongoUrl: process.env.MONGODB_URI,
        touchAfter: 24 * 3600 // Lazy session update
    })
}));

Content Security Policy

CSP for OAuth Applications

app.use((req, res, next) => {
    res.setHeader('Content-Security-Policy', [
        "default-src 'self'",
        "script-src 'self' 'unsafe-inline'", // Minimize unsafe-inline
        "style-src 'self' 'unsafe-inline'",
        "img-src 'self' data: https:",
        "connect-src 'self' https://account.oten.com",
        "frame-ancestors 'none'", // Prevent clickjacking
        "base-uri 'self'",
        "form-action 'self' https://account.oten.com"
    ].join('; '));
    
    next();
});

Security Monitoring

Monitor Authentication Events

function logSecurityEvent(event, details) {
    const logEntry = {
        timestamp: new Date().toISOString(),
        event: event,
        severity: getSeverity(event),
        details: details,
        source: 'oauth-service'
    };
    
    // Send to security monitoring system
    securityLogger.log(logEntry);
    
    // Alert on suspicious patterns
    if (logEntry.severity === 'HIGH') {
        alertingService.sendAlert(logEntry);
    }
}

// Usage examples
logSecurityEvent('login_success', { userId, ip, userAgent });
logSecurityEvent('login_failure', { username, ip, reason });
logSecurityEvent('token_refresh', { userId, ip });
logSecurityEvent('suspicious_activity', { userId, ip, details });

Rate Limiting

const rateLimit = require('express-rate-limit');

// Rate limit for login attempts
const loginLimiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 5, // Limit each IP to 5 requests per windowMs
    message: 'Too many login attempts, please try again later',
    standardHeaders: true,
    legacyHeaders: false,
    keyGenerator: (req) => {
        // Rate limit by IP and username combination
        return `${req.ip}:${req.body.username || 'unknown'}`;
    }
});

app.post('/auth/callback', loginLimiter, handleCallback);
if (req.header('x-forwarded-proto') !== 'https') {
res.redirect(`https://${req.header('host')}${req.url}`);
} else {
next();
}
});

📖 Comprehensive PKCE Guide: For complete implementation examples for SPAs and native apps, see the PKCE Implementation Guide.