Skip to main content

Vanilla JavaScript Quickstart

Add authentication to any JavaScript application without frameworks.


Before You Begin

info

You need these credentials before starting. Get them from the SyAuth Dashboard.

What You NeedWhere to Find ItExample
Client IDDashboard → OAuth Clients → Your Appa1b2c3d4-5678-90ab-cdef-...

New to SyAuth? Follow these steps first:

  1. Create a Nexorix account (SyAuth uses Nexorix for authentication)
  2. Create your first OAuth Client to get your Client ID
  3. Choose Public Client type (for browser-based apps without a backend)
  4. Add your callback URL (e.g., http://localhost:3000/callback.html) as a Redirect URI

Prerequisites

  • A basic HTML/JS application (no framework required)
  • A modern browser with Web Crypto API support (all modern browsers)

How It Works

Here's what happens when a user logs in:


Step 1: Create Configuration

Create a JavaScript file with your SyAuth configuration:

// syauth-config.js

const SyAuthConfig = {
// The SyAuth API endpoint (don't change this unless self-hosting)
apiUrl: 'https://api.syauth.com/e/v1',

// Your Client ID from Dashboard → OAuth Clients → Your App
clientId: 'your-client-id-here',

// Where SyAuth redirects after login - must match Dashboard config!
redirectUri: 'http://localhost:3000/callback.html',

// Permissions to request (openid profile email is standard)
scope: 'openid profile email',
};
tip

Where to find your Client ID:

  1. Log in to syauth.com/dashboard
  2. Select your Workspace
  3. Click OAuth Clients in the sidebar
  4. Click on your application
  5. Copy the Client ID

Step 2: Create PKCE Utility Functions

PKCE is a security feature that protects against authorization code interception attacks. It's required for browser-based applications.

// syauth-crypto.js

/**
* Generates a cryptographically random string for PKCE
* @returns {string} A random URL-safe string
*/
function generateRandomString(length = 64) {
const array = new Uint8Array(length);
crypto.getRandomValues(array);

// Convert to URL-safe base64
return btoa(String.fromCharCode(...array))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}

/**
* Creates a SHA-256 hash of the code verifier for PKCE
* @param {string} codeVerifier - The random string to hash
* @returns {Promise<string>} Base64URL-encoded hash
*/
async function generateCodeChallenge(codeVerifier) {
// Encode the verifier as bytes
const encoder = new TextEncoder();
const data = encoder.encode(codeVerifier);

// Create SHA-256 hash
const digest = await crypto.subtle.digest('SHA-256', data);

// Convert to URL-safe base64
return btoa(String.fromCharCode(...new Uint8Array(digest)))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}

Step 3: Create the Auth Module

This module handles login, logout, and token management:

// syauth-auth.js

const SyAuth = {
/**
* Initiates the login flow by redirecting to SyAuth
*/
async login() {
// Step 1: Generate PKCE codes
const codeVerifier = generateRandomString(64);
const codeChallenge = await generateCodeChallenge(codeVerifier);

// Step 2: Generate state for CSRF protection
const state = generateRandomString(32);

// Step 3: Store these for later (we'll need them in the callback)
sessionStorage.setItem('syauth_code_verifier', codeVerifier);
sessionStorage.setItem('syauth_state', state);

// Step 4: Build the authorization URL
const params = new URLSearchParams({
response_type: 'code',
client_id: SyAuthConfig.clientId,
redirect_uri: SyAuthConfig.redirectUri,
scope: SyAuthConfig.scope,
state: state,
code_challenge: codeChallenge,
code_challenge_method: 'S256',
});

// Step 5: Redirect user to SyAuth login page
window.location.href = `${SyAuthConfig.apiUrl}/oauth/authorize?${params}`;
},

/**
* Handles the OAuth callback after login
* Call this on your callback page
* @returns {Promise<Object>} The authenticated user's tokens
*/
async handleCallback() {
const params = new URLSearchParams(window.location.search);
const code = params.get('code');
const state = params.get('state');
const error = params.get('error');

// Check for errors from SyAuth
if (error) {
throw new Error(`Authentication failed: ${error}`);
}

// Verify we have an authorization code
if (!code) {
throw new Error('No authorization code received');
}

// SECURITY: Verify state matches what we sent (prevents CSRF)
const storedState = sessionStorage.getItem('syauth_state');
if (state !== storedState) {
throw new Error('Invalid state - possible CSRF attack!');
}

// Get the PKCE verifier we stored earlier
const codeVerifier = sessionStorage.getItem('syauth_code_verifier');
if (!codeVerifier) {
throw new Error('Missing PKCE verifier - did you start from /login?');
}

// Exchange the authorization code for tokens
const response = await fetch(`${SyAuthConfig.apiUrl}/oauth/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: SyAuthConfig.clientId,
code: code,
redirect_uri: SyAuthConfig.redirectUri,
code_verifier: codeVerifier,
}),
});

if (!response.ok) {
const error = await response.json();
throw new Error(error.error_description || 'Token exchange failed');
}

const tokens = await response.json();

// Store tokens (using sessionStorage for demo - see security note below)
sessionStorage.setItem('syauth_access_token', tokens.access_token);
if (tokens.refresh_token) {
sessionStorage.setItem('syauth_refresh_token', tokens.refresh_token);
}

// Clean up PKCE data
sessionStorage.removeItem('syauth_code_verifier');
sessionStorage.removeItem('syauth_state');

return tokens;
},

/**
* Gets the current access token
* @returns {string|null}
*/
getAccessToken() {
return sessionStorage.getItem('syauth_access_token');
},

/**
* Checks if user is authenticated
* @returns {boolean}
*/
isAuthenticated() {
return !!this.getAccessToken();
},

/**
* Fetches the current user's profile
* @returns {Promise<Object>} User profile data
*/
async getUser() {
const token = this.getAccessToken();
if (!token) {
throw new Error('Not authenticated');
}

const response = await fetch(`${SyAuthConfig.apiUrl}/api/user/profile`, {
headers: { 'Authorization': `Bearer ${token}` },
});

if (!response.ok) {
throw new Error('Failed to fetch user profile');
}

return response.json();
},

/**
* Logs out the user
*/
logout() {
sessionStorage.removeItem('syauth_access_token');
sessionStorage.removeItem('syauth_refresh_token');

// Optionally redirect to login page
window.location.href = '/';
},
};
CAUTION

Security Note: In production, avoid storing tokens in localStorage or sessionStorage as they're vulnerable to XSS attacks. Consider:

  • Using HTTP-only cookies (requires a backend)
  • Storing tokens in memory only
  • Using a secure backend proxy for API calls

Step 4: Create Your HTML Pages

Login Page (index.html)

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My App</title>
</head>
<body>
<div id="app">
<h1>Welcome to My App</h1>

<!-- Show this when NOT logged in -->
<div id="login-section">
<p>Please sign in to continue.</p>
<button onclick="SyAuth.login()">Sign In with SyAuth</button>
</div>

<!-- Show this when logged in -->
<div id="user-section" style="display: none;">
<p>Welcome, <span id="user-name"></span>!</p>
<p>Email: <span id="user-email"></span></p>
<button onclick="SyAuth.logout()">Sign Out</button>
</div>
</div>

<!-- Include scripts in order -->
<script src="syauth-config.js"></script>
<script src="syauth-crypto.js"></script>
<script src="syauth-auth.js"></script>
<script>
// Check if user is authenticated on page load
async function init() {
if (SyAuth.isAuthenticated()) {
try {
const user = await SyAuth.getUser();

// Show user info
document.getElementById('user-name').textContent = user.first_name;
document.getElementById('user-email').textContent = user.email;

document.getElementById('login-section').style.display = 'none';
document.getElementById('user-section').style.display = 'block';
} catch (error) {
// Token might be expired
console.error('Auth check failed:', error);
SyAuth.logout();
}
}
}

init();
</script>
</body>
</html>

Callback Page (callback.html)

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Authenticating...</title>
</head>
<body>
<div id="message">
<p>Authenticating, please wait...</p>
</div>

<script src="syauth-config.js"></script>
<script src="syauth-crypto.js"></script>
<script src="syauth-auth.js"></script>
<script>
async function processCallback() {
try {
await SyAuth.handleCallback();

// Success! Redirect to the main app
window.location.href = '/';

} catch (error) {
// Show error to user
document.getElementById('message').innerHTML = `
<h2>Authentication Failed</h2>
<p style="color: red;">${error.message}</p>
<a href="/">Go back and try again</a>
`;
}
}

processCallback();
</script>
</body>
</html>

File Structure

Your project should look like this:

your-project/
├── index.html # Main page with login button
├── callback.html # OAuth callback handler
├── syauth-config.js # Your SyAuth configuration
├── syauth-crypto.js # PKCE utility functions
└── syauth-auth.js # Authentication module

Test Your Integration

  1. Start a local server (Python example):

    python -m http.server 3000
  2. Open http://localhost:3000

  3. Click "Sign In with SyAuth"

    • You'll be redirected to the SyAuth login page
    • Log in or create an account
    • You'll be redirected back to your app, now authenticated!

Done! 🎉

Your vanilla JavaScript app now has:

  • ✅ OAuth 2.0 with PKCE (secure even in browsers)
  • ✅ No framework dependencies
  • ✅ Login and logout functionality
  • ✅ User profile retrieval

Troubleshooting

"Invalid redirect_uri" Error

Cause: The redirect URI doesn't match your Dashboard configuration.

Fix:

  1. Go to Dashboard → OAuth Clients → Your App → Redirect URIs
  2. Ensure http://localhost:3000/callback.html is listed exactly

"Invalid state" Error

Cause: State mismatch - usually happens if you open the login in a new tab.

Fix: Always start login from the same tab. Don't share login URLs.

"Missing PKCE verifier" Error

Cause: The callback page was loaded without going through login first.

Fix: Don't navigate directly to /callback.html. Always start from the login flow.

CORS Errors

Cause: Trying to call the API from a file:// URL or wrong origin.

Fix: Use a local HTTP server (like python -m http.server) instead of opening HTML files directly.


Next Steps