Core Authentication
This guide covers all authentication methods available in the Memberstack DOM package. The examples use vanilla JavaScript and can be adapted to any framework.
Complete the Quick Start Guide and initialize Memberstack in your project with your public key before proceeding.
Email/Password Flow
The traditional email and password authentication method provides a familiar login experience. This example includes loading states and error handling for a better user experience.
// Add this HTML to your login page
<form id="loginForm">
<input type="email" id="email" placeholder="Email" />
<input type="password" id="password" placeholder="Password" />
<button type="submit" id="loginButton">Log In</button>
<div id="loginError" style="display: none; color: red;"></div>
</form>
// Add this JavaScript to handle the login
document.getElementById('loginForm').addEventListener('submit', async (event) => {
event.preventDefault();
const loginButton = document.getElementById('loginButton');
const errorDiv = document.getElementById('loginError');
// Disable button and show loading state
loginButton.disabled = true;
loginButton.textContent = 'Logging in...';
errorDiv.style.display = 'none';
// Get form values
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
try {
const { data } = await memberstack.loginMemberEmailPassword({
email,
password
});
// If login is successful, redirect to dashboard
window.location.href = "/dashboard";
} catch (error) {
errorDiv.textContent = error.message || "Login failed. Please check your credentials.";
errorDiv.style.display = 'block';
} finally {
// Reset button state
loginButton.disabled = false;
loginButton.textContent = 'Log In';
}
});- Always show loading states during authentication
- Provide clear error messages for validation failures
- Implement proper client-side email validation
- Include password strength indicators
Passwordless Flow
Passwordless authentication provides a secure and user-friendly alternative to traditional passwords. Users receive a six-digit code via email that automatically logs them in. You can configure the passwordless email template in the Memberstack dashboard under "Emails" > "Transactional" > "Passwordless Auth".
// Add this HTML to your login page
// Step 1: request the code by email
<form id="passwordlessForm">
<input type="email" id="passwordlessEmail" placeholder="Email" />
<button type="submit" id="passwordlessButton">Login with passwordless</button>
<div id="passwordlessMessage"></div>
<div id="passwordlessError" style="display: none; color: red;"></div>
</form>
// Step 2: enter the six-digit code from the email (hidden until step 1 succeeds)
<form id="verificationForm" style="display: none;">
<input type="text" id="passwordlessCode" placeholder="Six-digit code" inputmode="numeric" />
<button type="submit" id="verificationButton">Verify & log in</button>
<div id="verificationError" style="display: none; color: red;"></div>
</form>
// Add this JavaScript to handle passwordless login
let isEmailSent = false;
let pendingEmail = '';
const form = document.getElementById('passwordlessForm');
const button = document.getElementById('passwordlessButton');
const messageDiv = document.getElementById('passwordlessMessage');
const errorDiv = document.getElementById('passwordlessError');
const verificationForm = document.getElementById('verificationForm');
const verificationButton = document.getElementById('verificationButton');
const verificationError = document.getElementById('verificationError');
form.addEventListener('submit', async (event) => {
event.preventDefault();
if (isEmailSent) return; // Prevent multiple sends
button.disabled = true;
button.textContent = 'Sending...';
errorDiv.style.display = 'none';
const email = document.getElementById('passwordlessEmail').value;
try {
await memberstack.sendMemberLoginPasswordlessEmail({
email
});
isEmailSent = true;
pendingEmail = email; // Remember the email for the verification step
messageDiv.textContent = 'Check your email for the login code';
button.textContent = 'Check your email';
// Reveal step 2 so the user can enter the code
verificationForm.style.display = 'block';
// Reset after 60 seconds
setTimeout(() => {
isEmailSent = false;
button.disabled = false;
button.textContent = 'Login with passwordless';
messageDiv.textContent = '';
}, 60000);
} catch (error) {
errorDiv.textContent = error.message || "Couldn't send login email. Please try again.";
errorDiv.style.display = 'block';
button.disabled = false;
button.textContent = 'Login with passwordless';
}
});
// Step 2: read the code from the form and verify it
verificationForm.addEventListener('submit', (event) => {
event.preventDefault();
const token = document.getElementById('passwordlessCode').value.trim();
handlePasswordlessVerification(pendingEmail, token);
});
// Handle verification
async function handlePasswordlessVerification(email, token) {
verificationButton.disabled = true;
verificationButton.textContent = 'Verifying...';
verificationError.style.display = 'none';
try {
const member = await memberstack.loginMemberPasswordless({
email: email,
passwordlessToken: token
});
window.location.href = "/dashboard";
} catch (error) {
console.error("Verification failed:", error);
verificationError.textContent = error.message || 'Invalid or expired code. Please try again.';
verificationError.style.display = 'block';
verificationButton.disabled = false;
verificationButton.textContent = 'Verify & log in';
}
}- Implement rate limiting for passwordless email requests
- Show clear instructions after the code is sent
- Handle code expiration gracefully
- Provide a way to request a new code if needed
- Consider adding a backup login method
Passwordless Signup
The same passwordless mechanism works for creating new accounts. Send a signup code with sendMemberSignupPasswordlessEmail, then complete signup with signupMemberPasswordless (passing the code as passwordlessToken). Just like email/password signup, you can attach customFields and free plans. On success the new member is signed in and persisted automatically.
// 1. Send the signup code to the member's email
await memberstack.sendMemberSignupPasswordlessEmail({
email: "newuser@example.com"
});
// Resolves with { data: { success: true } } once the email is queued.
// 2. Complete signup with the code the member received by email
async function completePasswordlessSignup(email, code) {
const { data } = await memberstack.signupMemberPasswordless({
email,
passwordlessToken: code, // the code from the email
// Optional, same as email/password signup:
customFields: { "first-name": "Jane" },
plans: [{ planId: "pln_free" }]
});
// The new member is signed in and persisted automatically.
window.location.href = data.redirect || "/dashboard";
}Social Provider Flow
Social authentication allows users to log in using their existing accounts from supported providers. This can significantly reduce friction in the signup and login process.
Before implementing social login, configure your providers in the Memberstack dashboard:
- Navigate to your Memberstack dashboard
- Go to "Settings" > "Auth Providers"
- Enable and configure each provider you want to use
- Add authorized domains and callback URLs
// Add this HTML for social login buttons
<div class="social-login-buttons">
<button onclick="loginWithProvider('google')" id="googleLogin">Login with Google</button>
<button onclick="loginWithProvider('github')" id="githubLogin">Login with GitHub</button>
<button onclick="loginWithProvider('facebook')" id="facebookLogin">Login with Facebook</button>
<!-- Add more provider buttons as needed -->
</div>
<div id="socialLoginError" style="display: none; color: red;"></div>
// Add this JavaScript to handle social login
const PROVIDERS = {
GOOGLE: "google",
FACEBOOK: "facebook",
MICROSOFT: "microsoft",
GITHUB: "github",
LINKEDIN: "linkedin",
SPOTIFY: "spotify",
DRIBBBLE: "dribbble"
};
async function loginWithProvider(provider) {
const button = document.getElementById(provider + 'Login');
const errorDiv = document.getElementById('socialLoginError');
button.disabled = true;
errorDiv.style.display = 'none';
try {
const { data } = await memberstack.loginWithProvider({
provider,
allowSignup: true // If user doesn't exist, sign them up
});
// loginWithProvider opens a popup for the OAuth flow and RESOLVES once the
// user finishes, this line runs after. The session is already stored for
// you (onAuthChange has fired), so you can safely redirect now.
window.location.href = data.redirect || "/dashboard";
} catch (error) {
errorDiv.textContent = `${provider} login failed: ${error.message}`;
errorDiv.style.display = 'block';
} finally {
button.disabled = false;
}
}Supported Providers
- Microsoft
- GitHub
- Spotify
- Dribbble
Implementation Tips
- Use official provider logos
- Handle connection failures gracefully
- Implement proper scope handling
- Consider data mapping strategies
Signing Up with a Provider
loginWithProvider (with allowSignup: true) handles both login and signup. If you want an explicit signup-only entry point, and to pre-set custom fields or free plans on the new account, use signupWithProvider. Like loginWithProvider, it opens a popup and resolves with { data: { member, tokens, redirect } } once the user finishes; the new member is signed in and persisted automatically.
// Sign up a new member with a social provider
async function signupWithGoogle() {
try {
const { data } = await memberstack.signupWithProvider({
provider: "google",
// Optional: prefill custom fields and assign free plans on signup
customFields: { "sign-up-source": "google" },
plans: [{ planId: "pln_free" }]
});
// Opens a popup. This runs AFTER the user finishes, the new member is
// already signed in and persisted (onAuthChange has fired).
console.log("Signed up:", data.member.auth.email);
window.location.href = data.redirect || "/welcome";
} catch (error) {
// e.g. "use-email-login" if that email already has a password account
console.error("Provider signup failed:", error.message);
}
}Connecting & Disconnecting Providers
Let a logged-in member link additional providers to their account. connectProvider opens a popup; disconnectProvider is a direct request (no popup). Both resolve with { data: { providers, message } }, where providers is the member's full list of connected providers afterward.
// Connect an additional provider to the LOGGED-IN member (opens a popup)
async function connectGoogle() {
const { data } = await memberstack.connectProvider({ provider: "google" });
// data.providers is the member's full provider list after connecting
console.log("Connected providers:", data.providers);
}
// Disconnect a provider (no popup, a direct request)
async function disconnectGoogle() {
try {
const { data } = await memberstack.disconnectProvider({ provider: "google" });
console.log("Remaining providers:", data.providers);
} catch (error) {
// "account-not-connected" if the member isn't connected to that provider
console.error(error.message);
}
}Before disconnecting a member's only login method, make sure they have another way in (a password or another provider), otherwise they could lock themselves out. If the provider is their only credential, have them setPassword() first (below).
Password Reset & Setting a Password
These methods power the "forgot password" flow programmatically (the pre-built modals do the same with no code). Reset is a two-step flow: request an email, then complete the reset with the token from that email.
Forgot Password (reset flow)
// Step 1, send the reset email. For security this resolves the SAME whether
// or not the email exists, and the resolved `data` IS the message string.
async function requestPasswordReset(email) {
const { data: message } = await memberstack.sendMemberResetPasswordEmail({ email });
console.log(message);
// "If this email exists, you'll receive an email with the reset instructions."
}
// Step 2, complete the reset with the token from the email link.
async function completePasswordReset(token, newPassword) {
try {
const { data } = await memberstack.resetMemberPassword({ token, newPassword });
if (data.success) {
// NOTE: this does NOT log the member in, send them to your login page.
window.location.href = "/login";
}
} catch (error) {
// "invalid-reset-code" if the token is wrong or expired
console.error(error.message);
}
}Setting a Password (passwordless & social members)
Members who signed up with a provider or passwordless email have no password (auth.hasPassword === false). Use setPassword to add one so they can also log in with email + password. The member must be logged in, and no old password is required. It resolves with the updated member ({ data: member }).
// Add a password to a passwordless / social member (must be logged in).
async function addPassword(newPassword) {
const { data: member } = await memberstack.setPassword({ password: newPassword });
console.log("Password login enabled:", member.auth.hasPassword); // true
}Use setPassword to add a first password (no old password needed). To change an existing password, use updateMemberAuth({ oldPassword, newPassword }), see Password Management.
Logout & Session Management
In this section, you'll learn how to handle user sessions, implement a logout function, and manage session expiration.
Logout Function
Implementing a logout function is essential for user privacy and security. This example shows how to handle logout and clear the session:
// Handle logout
async function handleLogout() {
try {
await memberstack.logout();
window.location.href = "/login";
} catch (error) {
console.error("Logout failed:", error);
}
}Session Management
Proper session management is crucial for maintaining security and user experience. This guide will walk you through implementing robust session handling with Memberstack.
1. Basic Setup
First, configure Memberstack with session-specific options during initialization:
const memberstack = memberstackDOM.init({
publicKey: "YOUR_PUBLIC_KEY",
useCookies: true, // Enable cookie persistence
setCookieOnRootDomain: true, // Optional: set cookies on root domain
});Memberstack supports both localStorage (default) and cookie-based storage for session management. Choose based on your specific needs:
- useCookies (optional): Set to true to use cookies instead of localStorage; use for cross-tab synchronization needs; consider for multi-subdomain applications
- setCookieOnRootDomain (optional): Only relevant when useCookies is true
- appId (optional): String app ID for multi-app / multi-tenant setups; sent as the
X-APP-IDheader on requests. Only needed if you have multiple Memberstack apps - domain (optional, advanced): Point the SDK at a custom API domain for enterprise or self-hosted deployments. The default host is
client.memberstack.com; most users should leave this unset
2. Authentication State Management
Implement a listener to track authentication state changes and update your UI accordingly:
// Add this HTML for auth state display
<div id="authStateContainer">
<div id="loadingState">Loading...</div>
<div id="authenticatedState" style="display: none;">
<p>Welcome, <span id="memberEmail"></span></p>
<p>Session expires: <span id="sessionExpiry"></span></p>
<button onclick="handleLogout()">Log Out</button>
</div>
<div id="unauthenticatedState" style="display: none;">
Please log in
</div>
</div>
// JavaScript for auth state management
document.addEventListener('DOMContentLoaded', () => {
const loadingState = document.getElementById('loadingState');
const authenticatedState = document.getElementById('authenticatedState');
const unauthenticatedState = document.getElementById('unauthenticatedState');
const memberEmailSpan = document.getElementById('memberEmail');
const sessionExpirySpan = document.getElementById('sessionExpiry');
// Listen for authentication changes
const unsubscribe = memberstack.onAuthChange((member) => {
loadingState.style.display = 'none';
if (member) {
authenticatedState.style.display = 'block';
unauthenticatedState.style.display = 'none';
memberEmailSpan.textContent = member.auth.email;
// Display session information
const token = memberstack.getMemberCookie();
if (token) {
const tokenData = parseJwt(token);
const expiryDate = new Date(tokenData.exp * 1000);
sessionExpirySpan.textContent = expiryDate.toLocaleString();
}
} else {
authenticatedState.style.display = 'none';
unauthenticatedState.style.display = 'block';
}
});
});The onAuthChange callback receives the member object directly (or null), NOT a wrapper like { member, token }. This differs from auth methods like loginMemberEmailPassword(), which resolve to { data: { tokens, member, redirect } } — the member is nested at data.member, alongside the auth tokens and redirect.
3. Session Token Handling
Add these helper functions to work with session tokens:
// Parse JWT token to get expiration
function parseJwt(token) {
try {
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(atob(base64).split('').map(c =>
'%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
).join(''));
return JSON.parse(jsonPayload);
} catch (error) {
console.error('Error parsing token:', error);
return {};
}
}4. Session Refresh
Implement session refresh functionality to extend user sessions when needed:
// Add a refresh button to your UI
<button onclick="refreshSession()">Extend Session</button>
// Session refresh function
async function refreshSession() {
try {
await memberstack.getCurrentMember();
const token = memberstack.getMemberCookie();
if (token) {
const tokenData = parseJwt(token);
const expiryDate = new Date(tokenData.exp * 1000);
document.getElementById('sessionExpiry').textContent = expiryDate.toLocaleString();
}
} catch (error) {
console.error("Session refresh failed:", error);
}
}5. Session Expiration Handling
Add proactive session expiration checking to improve user experience:
// Check session expiration status
function checkSessionExpiration() {
const token = memberstack.getMemberCookie();
if (token) {
const tokenData = parseJwt(token);
const expiryTime = tokenData.exp * 1000;
const timeUntilExpiry = expiryTime - Date.now();
if (timeUntilExpiry < 300000) { // Less than 5 minutes
alert("Your session will expire soon. Please refresh your session.");
}
}
}
// Check session status periodically
setInterval(checkSessionExpiration, 60000); // Check every minuteCommon Issues & Solutions
- Need cross-tab session sync? Consider enabling useCookies
- Sessions expiring too quickly? Review your cookie and auth configuration
- Working with subdomains? Review your domain configuration
Best Practices
- Always use onAuthChange for state management
- Implement proactive session refresh
- Handle expired sessions gracefully