- Fixed bug related to null/undefined checks in the codebase
- Passflow JavaScript SDK Documentation
import { Passflow } from "passflow-js";
// Minimal initialization with just the required appId
const passflow = new Passflow({
appId: "your-app-id",
});
// Simple session setup
passflow.session({
createSession: () => console.log("User is authenticated"),
expiredSession: () => console.log("User session expired"),
});
// Basic sign in with just email and password
const simpleSignIn = async () => {
try {
await passflow.signIn({
email: "user@example.com",
password: "password123",
});
console.log("Signed in successfully");
} catch (error) {
console.error("Sign in failed", error);
}
};
// Basic sign up with minimal user info
const simpleSignUp = async () => {
try {
await passflow.signUp({
user: {
email: "user@example.com",
password: "password123",
},
});
console.log("Registered successfully");
} catch (error) {
console.error("Registration failed", error);
}
};
// Simple logout
const simpleSignOut = async () => {
await passflow.logOut();
};
// Start passwordless flow with just email
const simplePasswordless = async () => {
try {
const response = await passflow.passwordlessSignIn({
email: "user@example.com",
challenge_type: "otp",
redirect_url: window.location.origin,
});
console.log("Check your email for the code");
return response.challenge_id;
} catch (error) {
console.error("Failed to start passwordless flow", error);
}
};
// Complete with just the challenge ID and OTP
const completeSimplePasswordless = async (challengeId, otp) => {
try {
await passflow.passwordlessSignInComplete({
challenge_id: challengeId,
otp: otp,
});
console.log("Authentication successful");
} catch (error) {
console.error("Failed to complete passwordless flow", error);
}
};
// Register a passkey with minimal options
const simplePasskeyRegister = async () => {
try {
await passflow.passkeyRegister({
relying_party_id: window.location.hostname,
redirect_url: window.location.origin,
scopes: ["id", "offline"],
});
console.log("Passkey registered");
} catch (error) {
console.error("Passkey registration failed", error);
}
};
// Authenticate with minimal options
const simplePasskeyAuthenticate = async () => {
try {
await passflow.passkeyAuthenticate({
relying_party_id: window.location.hostname,
});
console.log("Authenticated with passkey");
} catch (error) {
console.error("Passkey authentication failed", error);
}
};
// Send reset email with just the email address
const simplePasswordReset = async () => {
try {
await passflow.sendPasswordResetEmail({
email: "user@example.com",
});
console.log("Password reset email sent");
} catch (error) {
console.error("Failed to send reset email", error);
}
};
// Create a tenant with just a name
const simpleCreateTenant = async () => {
try {
await passflow.createTenant("My Organization");
console.log("Tenant created");
} catch (error) {
console.error("Failed to create tenant", error);
}
};
// Subscribe to authentication events with minimal setup
passflow.subscribe({
onAuthChange: (eventType) => {
console.log(`Auth event occurred: ${eventType}`);
},
});
Passflow JavaScript SDK is a client library for interacting with the Passflow authentication service. It provides a comprehensive set of features for user authentication, token management, tenant management, and more. The SDK supports various authentication methods including email/password, passwordless, passkeys (WebAuthn), and federated identity providers.
npm install passflow-js
# or
yarn add passflow-js
Import and initialize the Passflow client with your configuration:
import { Passflow } from "passflow-js";
const passflow = new Passflow({
url: "https://auth.passflow.cloud", // or your custom URL
appId: "your-app-id",
scopes: [
"id",
"offline",
"tenant",
"email",
"oidc",
"openid",
"access:tenant:all",
], // optional, these are the defaults
createTenantForNewUser: false, // optional
parseQueryParams: true, // optional, will parse tokens from URL query params
keyStoragePrefix: "myapp", // optional, prefix for localStorage keys
});
Setup session management to handle authentication state:
passflow.session({
createSession: (tokens) => {
console.log("Session created", tokens);
// Set your app's authenticated state
},
expiredSession: () => {
console.log("Session expired");
// Clear your app's authenticated state
},
doRefresh: true, // automatically refresh tokens when expired
});
// Sign in with email and password
const signIn = async () => {
try {
const response = await passflow.signIn({
email: "user@example.com",
password: "password123",
scopes: ["id", "offline", "tenant", "email"], // optional
});
console.log("Signed in successfully", response);
} catch (error) {
console.error("Sign in failed", error);
}
};
// Register a new user
const signUp = async () => {
try {
const response = await passflow.signUp({
user: {
email: "user@example.com",
password: "password123",
given_name: "John",
family_name: "Doe",
// Additional optional user fields
},
scopes: ["id", "offline", "tenant", "email"], // optional
create_tenant: true, // optional
});
console.log("Registered successfully", response);
} catch (error) {
console.error("Registration failed", error);
}
};
// Log out the current user
const signOut = async () => {
try {
await passflow.logOut();
console.log("Signed out successfully");
} catch (error) {
console.error("Sign out failed", error);
}
};
// Start passwordless authentication flow
const startPasswordless = async () => {
try {
const response = await passflow.passwordlessSignIn({
email: "user@example.com",
challenge_type: "otp", // or 'magic_link'
redirect_url: "https://yourapp.com/auth/callback",
});
console.log("Passwordless authentication started", response);
// Store the challenge_id for the next step
} catch (error) {
console.error("Passwordless authentication failed", error);
}
};
// Complete passwordless authentication flow
const completePasswordless = async (challengeId, otp) => {
try {
const response = await passflow.passwordlessSignInComplete({
challenge_id: challengeId,
otp: otp,
});
console.log("Passwordless authentication completed", response);
} catch (error) {
console.error("Passwordless authentication completion failed", error);
}
};
// Sign in with a provider using popup
passflow.federatedAuthWithPopup(
"google", // or 'facebook'
"https://yourapp.com/auth/callback",
["id", "offline", "tenant", "email"] // optional scopes
);
// Sign in with a provider using redirect
passflow.federatedAuthWithRedirect(
"google", // or 'facebook'
"https://yourapp.com/auth/callback",
["id", "offline", "tenant", "email"] // optional scopes
);
// Register a new passkey
const registerPasskey = async () => {
try {
const response = await passflow.passkeyRegister({
passkey_display_name: "My Passkey",
passkey_username: "user@example.com",
relying_party_id: window.location.hostname,
redirect_url: "https://yourapp.com/auth/callback",
scopes: ["id", "offline", "tenant", "email"], // optional
});
console.log("Passkey registered successfully", response);
} catch (error) {
console.error("Passkey registration failed", error);
}
};
// Authenticate with a passkey
const authenticateWithPasskey = async () => {
try {
const response = await passflow.passkeyAuthenticate({
relying_party_id: window.location.hostname,
scopes: ["id", "offline", "tenant", "email"], // optional
});
console.log("Passkey authentication successful", response);
} catch (error) {
console.error("Passkey authentication failed", error);
}
};
// Add additional passkey to user account
const addPasskey = async () => {
try {
await passflow.addUserPasskey({
relyingPartyId: window.location.hostname,
passkeyUsername: "user@example.com",
passkeyDisplayName: "My Secondary Passkey",
});
console.log("Passkey added successfully");
} catch (error) {
console.error("Adding passkey failed", error);
}
};
// Manage user passkeys
const managePasskeys = async () => {
try {
// Get all passkeys
const passkeys = await passflow.getUserPasskeys();
console.log("User passkeys", passkeys);
// Rename a passkey
await passflow.renameUserPasskey("New Name", "passkey-id");
// Delete a passkey
await passflow.deleteUserPasskey("passkey-id");
} catch (error) {
console.error("Passkey management failed", error);
}
};
// Send password reset email
const sendResetEmail = async () => {
try {
await passflow.sendPasswordResetEmail({
email: "user@example.com",
reset_page_url: "https://yourapp.com/reset-password",
});
console.log("Password reset email sent");
} catch (error) {
console.error("Sending password reset email failed", error);
}
};
// Reset password (after receiving reset token)
const resetPassword = async (newPassword) => {
try {
// The token should be in the URL query parameters
const response = await passflow.resetPassword(newPassword);
console.log("Password reset successful", response);
} catch (error) {
console.error("Password reset failed", error);
}
};
// Check if user is authenticated
const isAuthenticated = passflow.isAuthenticated();
// Get current tokens
const tokens = passflow.getTokensCache();
// Get parsed tokens (decoded JWT payload)
const parsedTokens = passflow.getParsedTokenCache();
// Manually refresh token
const refreshToken = async () => {
try {
const response = await passflow.refreshToken();
console.log("Token refreshed", response);
} catch (error) {
console.error("Token refresh failed", error);
}
};
// Set tokens manually
const setTokens = async (tokens) => {
try {
await passflow.setTokens({
access_token: tokens.access_token,
refresh_token: tokens.refresh_token,
id_token: tokens.id_token,
scopes: tokens.scopes,
});
} catch (error) {
console.error("Setting tokens failed", error);
}
};
// Handle tokens from redirect
const handleRedirect = () => {
const tokens = passflow.handleTokensRedirect();
if (tokens) {
console.log("Tokens received from redirect", tokens);
}
};
// Create a new tenant
const createTenant = async () => {
try {
const response = await passflow.createTenant("My Organization", true);
console.log("Tenant created", response);
} catch (error) {
console.error("Tenant creation failed", error);
}
};
// Join a tenant via invitation
const joinTenant = async (invitationToken) => {
try {
const response = await passflow.joinInvitation(invitationToken);
console.log("Joined tenant", response);
} catch (error) {
console.error("Joining tenant failed", error);
}
};
// Request an invitation link
const requestInvite = async () => {
try {
const response = await passflow.requestInviteLink({
email: "newuser@example.com",
tenant: "tenant-id", // optional
group: "group-id", // optional
role: "role-name", // optional
callback: "https://yourapp.com/onboarding", // optional
send_to_email: true, // optional
});
console.log("Invitation link created", response);
} catch (error) {
console.error("Creating invitation link failed", error);
}
};
// Get all active invitations
const getInvitations = async () => {
try {
const invitations = await passflow.getInvitations({
tenant_id: "tenant-id", // optional
group_id: "group-id", // optional
skip: 0, // optional
limit: 10, // optional
});
console.log("Active invitations", invitations);
} catch (error) {
console.error("Getting invitations failed", error);
}
};
// Delete an invitation
const deleteInvitation = async (token) => {
try {
await passflow.deleteInvitation(token);
console.log("Invitation deleted");
} catch (error) {
console.error("Deleting invitation failed", error);
}
};
// Define a subscriber
const subscriber = {
onAuthChange: (eventType, source) => {
console.log(`Auth event: ${eventType}`, source);
// Handle different event types
switch (eventType) {
case "signin":
// Handle sign in
break;
case "signout":
// Handle sign out
break;
case "register":
// Handle registration
break;
case "error":
// Handle error
break;
case "refresh":
// Handle token refresh
break;
}
},
};
// Subscribe to all events
passflow.subscribe(subscriber);
// Subscribe to specific events
passflow.subscribe(subscriber, ["signin", "signout"]);
// Unsubscribe from all events
passflow.unsubscribe(subscriber);
// Unsubscribe from specific events
passflow.unsubscribe(subscriber, ["error"]);
Errors thrown by the SDK are typically instances of PassflowError
which include details about the error:
try {
await passflow.signIn({
email: "user@example.com",
password: "wrong-password",
});
} catch (error) {
if (error instanceof PassflowError) {
console.error(`Error ID: ${error.id}`);
console.error(`Error Message: ${error.message}`);
console.error(`Status Code: ${error.status}`);
console.error(`Location: ${error.location}`);
console.error(`Time: ${error.time}`);
} else {
console.error("Unknown error:", error);
}
}
The main class that provides access to all functionality of the SDK.
constructor(config: PassflowConfig)
Configuration options:
-
url
: The URL of the Passflow service (default: 'https://auth.passflow.cloud') -
appId
: Your application ID -
scopes
: Token scopes to request (default: ['id', 'offline', 'tenant', 'email', 'oidc', 'openid', 'access:tenant:all']) -
createTenantForNewUser
: Whether to create a tenant for new users (default: false) -
parseQueryParams
: Whether to parse tokens from URL query parameters (default: false) -
keyStoragePrefix
: Prefix for localStorage keys
Authentication Methods
Method | Description |
---|---|
session({ createSession, expiredSession, doRefresh }) |
Set up session management |
signIn(payload) |
Sign in with email/password |
signUp(payload) |
Register a new user |
logOut() |
Sign out the current user |
passwordlessSignIn(payload) |
Start passwordless authentication |
passwordlessSignInComplete(payload) |
Complete passwordless authentication |
federatedAuthWithPopup(provider, redirectUrl, scopes) |
Sign in with a provider using popup |
federatedAuthWithRedirect(provider, redirectUrl, scopes) |
Sign in with a provider using redirect |
passkeyRegister(payload) |
Register a new passkey |
passkeyAuthenticate(payload) |
Authenticate with a passkey |
sendPasswordResetEmail(payload) |
Send password reset email |
resetPassword(newPassword, scopes) |
Reset password |
Token Methods
Method | Description |
---|---|
isAuthenticated() |
Check if user is authenticated |
getTokensCache() |
Get current tokens |
getParsedTokenCache() |
Get parsed tokens |
refreshToken() |
Refresh the access token |
setTokens(tokens) |
Set tokens manually |
handleTokensRedirect() |
Handle tokens from redirect |
authRedirectUrl(options) |
Generate an auth redirect URL |
authRedirect(options) |
Redirect to the auth page |
Tenant Methods
Method | Description |
---|---|
createTenant(name, refreshToken) |
Create a new tenant |
joinInvitation(token, scopes) |
Join a tenant via invitation |
Invitation Methods
Method | Description |
---|---|
requestInviteLink(payload) |
Request an invitation link |
getInvitations(options) |
Get all active invitations |
deleteInvitation(token) |
Delete an invitation |
Passkey Methods
Method | Description |
---|---|
getUserPasskeys() |
Get all user passkeys |
renameUserPasskey(name, passkeyId) |
Rename a passkey |
deleteUserPasskey(passkeyId) |
Delete a passkey |
addUserPasskey(options) |
Add a passkey to user account |
Event Methods
Method | Description |
---|---|
subscribe(subscriber, events) |
Subscribe to auth events |
unsubscribe(subscriber, events) |
Unsubscribe from auth events |
The SDK includes several services that handle specific functionality:
-
AuthService
: Handles authentication and session management -
UserService
: Handles user-related operations -
TenantService
: Handles tenant operations -
InvitationService
: Handles invitation operations -
DeviceService
: Manages device identification -
TokenService
: Handles token parsing and validation -
StorageManager
: Manages token storage
Key types used in the SDK:
type PassflowConfig = {
url?: string;
appId?: string;
scopes?: string[];
createTenantForNewUser?: boolean;
parseQueryParams?: boolean;
keyStoragePrefix?: string;
};
type Tokens = {
access_token: string;
id_token?: string;
refresh_token?: string;
scopes?: string[];
};
type Token = {
aud: string[];
exp: number;
iat: number;
iss: string;
jti: string;
sub: string;
type: string;
email?: string;
passflow_tm?: RawUserMembership;
payload?: unknown;
membership?: UserMembership;
};
type PassflowSignInPayload = {
password: string;
scopes?: string[];
email?: string;
phone?: string;
username?: string;
} & ({ email: string } | { phone: string } | { username: string });
type PassflowSignUpPayload = {
user: PassflowUserPayload;
scopes?: string[];
create_tenant?: boolean;
anonymous?: boolean;
invite?: string;
};
type UserMembership = {
raw: RawUserMembership;
tenants: TenantMembership[];
};
enum PassflowEvent {
SignIn = "signin",
Register = "register",
SignOut = "signout",
Error = "error",
Refresh = "refresh",
}
import { Passflow, PassflowError } from "passflow-js";
// Initialize Passflow
const passflow = new Passflow({
appId: "your-app-id",
parseQueryParams: true,
});
// Set up session management
passflow.session({
createSession: (tokens) => {
// Store authentication state
localStorage.setItem("isAuthenticated", "true");
// Update UI
document.getElementById("login-form").style.display = "none";
document.getElementById("user-dashboard").style.display = "block";
},
expiredSession: () => {
// Clear authentication state
localStorage.removeItem("isAuthenticated");
// Update UI
document.getElementById("login-form").style.display = "block";
document.getElementById("user-dashboard").style.display = "none";
},
doRefresh: true,
});
// Handle form submission
document
.getElementById("login-form")
.addEventListener("submit", async (event) => {
event.preventDefault();
const email = document.getElementById("email").value;
const password = document.getElementById("password").value;
try {
await passflow.signIn({ email, password });
// Login successful - session callback will handle UI update
} catch (error) {
if (error instanceof PassflowError) {
document.getElementById("error-message").textContent = error.message;
} else {
document.getElementById("error-message").textContent =
"An unexpected error occurred";
console.error(error);
}
}
});
// Handle logout
document.getElementById("logout-button").addEventListener("click", async () => {
await passflow.logOut();
// Logout successful - session callback will handle UI update
});
// Check for redirect tokens on page load
window.addEventListener("load", () => {
const tokens = passflow.handleTokensRedirect();
if (tokens) {
console.log("Authenticated via redirect");
}
});
import { Passflow } from "passflow-js";
// Initialize Passflow
const passflow = new Passflow({
appId: "your-app-id",
});
// Set up session management
passflow.session({
createSession: (tokens) => {
console.log("Session created", tokens);
},
expiredSession: () => {
console.log("Session expired");
},
doRefresh: true,
});
// Handle passkey registration
document
.getElementById("register-passkey-button")
.addEventListener("click", async () => {
try {
const email = document.getElementById("email").value;
await passflow.passkeyRegister({
passkey_display_name: "My Passkey",
passkey_username: email,
relying_party_id: window.location.hostname,
redirect_url: window.location.origin,
scopes: ["id", "offline", "tenant", "email"],
});
alert("Passkey registered successfully!");
} catch (error) {
alert(`Passkey registration failed: ${error.message}`);
console.error(error);
}
});
// Handle passkey authentication
document
.getElementById("login-with-passkey-button")
.addEventListener("click", async () => {
try {
await passflow.passkeyAuthenticate({
relying_party_id: window.location.hostname,
});
alert("Authenticated with passkey successfully!");
} catch (error) {
alert(`Passkey authentication failed: ${error.message}`);
console.error(error);
}
});
import { Passflow, PassflowEvent } from "passflow-js";
// Initialize Passflow
const passflow = new Passflow({
appId: "your-app-id",
});
// Track current tenant
let currentTenant = null;
// Set up session management
passflow.session({
createSession: (tokens) => {
const parsedTokens = passflow.getParsedTokenCache();
if (parsedTokens?.access_token?.membership?.tenants?.length > 0) {
currentTenant = parsedTokens.access_token.membership.tenants[0];
updateTenantUI();
} else {
showCreateTenantUI();
}
},
expiredSession: () => {
currentTenant = null;
showLoginUI();
},
doRefresh: true,
});
// Subscribe to auth events
passflow.subscribe({
onAuthChange: (eventType) => {
if (
eventType === PassflowEvent.SignIn ||
eventType === PassflowEvent.Register
) {
const parsedTokens = passflow.getParsedTokenCache();
if (parsedTokens?.access_token?.membership?.tenants?.length > 0) {
currentTenant = parsedTokens.access_token.membership.tenants[0];
updateTenantUI();
} else {
showCreateTenantUI();
}
}
},
});
// Create tenant function
async function createNewTenant() {
const tenantName = document.getElementById("tenant-name").value;
try {
await passflow.createTenant(tenantName, true);
const parsedTokens = passflow.getParsedTokenCache();
currentTenant = parsedTokens.access_token.membership.tenants[0];
updateTenantUI();
} catch (error) {
console.error("Failed to create tenant", error);
alert(`Failed to create tenant: ${error.message}`);
}
}
// Invite user function
async function inviteUser() {
const email = document.getElementById("invite-email").value;
try {
const response = await passflow.requestInviteLink({
email,
tenant: currentTenant.tenant.id,
send_to_email: true,
});
alert(`Invitation sent to ${email}`);
console.log("Invitation link:", response.link);
} catch (error) {
console.error("Failed to invite user", error);
alert(`Failed to invite user: ${error.message}`);
}
}
// UI update functions
function updateTenantUI() {
document.getElementById("tenant-name-display").textContent =
currentTenant.tenant.name;
document.getElementById("tenant-id-display").textContent =
currentTenant.tenant.id;
document.getElementById("tenant-section").style.display = "block";
document.getElementById("create-tenant-section").style.display = "none";
}
function showCreateTenantUI() {
document.getElementById("tenant-section").style.display = "none";
document.getElementById("create-tenant-section").style.display = "block";
}
function showLoginUI() {
document.getElementById("tenant-section").style.display = "none";
document.getElementById("create-tenant-section").style.display = "none";
document.getElementById("login-section").style.display = "block";
}
// Event listeners
document
.getElementById("create-tenant-form")
.addEventListener("submit", (e) => {
e.preventDefault();
createNewTenant();
});
document.getElementById("invite-form").addEventListener("submit", (e) => {
e.preventDefault();
inviteUser();
});