Vanilla JavaScript Quickstart
Add authentication to any JavaScript application without frameworks.
Before You Begin
You need these credentials before starting. Get them from the SyAuth Dashboard.
| What You Need | Where to Find It | Example |
|---|---|---|
| Client ID | Dashboard → OAuth Clients → Your App | a1b2c3d4-5678-90ab-cdef-... |
New to SyAuth? Follow these steps first:
- Create a Nexorix account (SyAuth uses Nexorix for authentication)
- Create your first OAuth Client to get your Client ID
- Choose Public Client type (for browser-based apps without a backend)
- 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',
};
Where to find your Client ID:
- Log in to syauth.com/dashboard
- Select your Workspace
- Click OAuth Clients in the sidebar
- Click on your application
- 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 = '/';
},
};
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 = `
`;
}
}
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
-
Start a local server (Python example):
python -m http.server 3000 -
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:
- Go to Dashboard → OAuth Clients → Your App → Redirect URIs
- Ensure
http://localhost:3000/callback.htmlis 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
- Quick Integration (Any Language) — See the full OAuth flow explained
- API Reference — Complete API documentation
- Security Best Practices — Important for production apps