@wristband/react-client-auth
Version:
A lightweight React SDK that pairs with your backend server auth to initialize and sync frontend sessions via secure session cookies.
134 lines (130 loc) • 5.94 kB
JavaScript
var index = require('../error/index.js');
var authProviderTypes = require('../types/auth-provider-types.js');
/* This module has browser conditionals that must be preserved */
const reservedLoginQueryKeys = ['login_hint', 'return_url', 'tenant_domain', 'tenant_custom_domain'];
const reservedLogoutQueryKeys = ['tenant_domain', 'tenant_custom_domain'];
/**
* Redirects the user to your backend server's Login Endpoint with optional configuration parameters (browser only).
*
* This function initiates a redirect to the specified login URL and appends relevant query parameters
* based on the provided configuration. The redirect will preserve the current page URL as the return
* destination by default, allowing users to resume where they left off after authentication.
*
* @param {string} loginUrl - The login endpoint URL that handles authentication
* @param {LoginRedirectConfig} config - Optional configuration for customizing the login experience
* @returns {Promise<void>} A promise that resolves when the redirect is triggered
* @throws {WristbandError} If loginUrl is undefined, null, or empty.
*
* @example
* // Basic redirect to login endpoint
* await redirectToLogin('/api/auth/login');
*
* @example
* // Redirect with pre-filled email address
* await redirectToLogin('/api/auth/login', {
* loginHint: 'user@example.com'
* });
*
* @example
* // Redirect with custom return destination and tenant domain
* await redirectToLogin('/api/auth/login', {
* returnUrl: 'https://app.example.com/dashboard',
* tenantDomain: 'acme-corp'
* });
*
* @example
* // Redirect with custom return destination and tenant domain
* await redirectToLogin('/api/auth/login', {
* returnUrl: 'https://app.example.com/dashboard',
* tenantCustomDomain: 'auth.acme.com'
* });
*/
function redirectToLogin(loginUrl, config = {}) {
if (!loginUrl) {
throw new index.WristbandError(authProviderTypes.WristbandErrorCode.INVALID_LOGIN_URL, 'Redirect To Login: "loginUrl" is required');
}
// For frameworks like NextJS, need to ensure this can only be attempted in the browser.
if (typeof window !== 'undefined') {
let resolvedUrl;
try {
resolvedUrl = new URL(loginUrl, window.location.origin);
}
catch {
throw new index.WristbandError(authProviderTypes.WristbandErrorCode.INVALID_LOGIN_URL, `Redirect To Login: "${loginUrl}" is not a valid login URL`);
}
for (const key of reservedLoginQueryKeys) {
if (resolvedUrl.searchParams.has(key)) {
throw new index.WristbandError(authProviderTypes.WristbandErrorCode.INVALID_LOGIN_URL, `Redirect To Login: loginUrl must not include reserved query param: "${key}"`);
}
}
const queryParams = new URLSearchParams({
...(config.loginHint ? { login_hint: config.loginHint } : {}),
...(config.returnUrl ? { return_url: encodeURI(config.returnUrl) } : {}),
...(config.tenantDomain ? { tenant_domain: config.tenantDomain } : {}),
...(config.tenantCustomDomain ? { tenant_custom_domain: config.tenantCustomDomain } : {}),
});
resolvedUrl.searchParams.forEach((value, key) => {
queryParams.append(key, value);
});
resolvedUrl.search = queryParams.toString();
window.location.href = resolvedUrl.toString();
}
}
/**
* Redirects the user to your backend server's Logout Endpoint with optional configuration (browser only).
*
* This function navigates the user to the specified logout URL and can append additional parameters as needed.
*
* @param {string} logoutUrl - The URL of your server's Logout Endpoint
* @param {LogoutRedirectConfig} config - Optional configuration for the logout redirect
* @returns {Promise<void>} A promise that resolves when the redirect is triggered
* @throws {WristbandError} If logoutUrl is undefined, null, or empty.
*
* @example
* // Basic redirect to logout endpoint
* await redirectToLogout('/api/auth/logout');
*
* @example
* // Redirect with tenant domain parameter
* await redirectToLogout('/api/auth/logout', {
* tenantDomain: 'acme-corp'
* });
*
* @example
* // Redirect with tenant domain parameter
* await redirectToLogout('/api/auth/logout', {
* tenantCustomDomain: 'auth.acme.com'
* });
*/
function redirectToLogout(logoutUrl, config = {}) {
if (!logoutUrl) {
throw new index.WristbandError(authProviderTypes.WristbandErrorCode.INVALID_LOGOUT_URL, 'Redirect To Logout: "logoutUrl" is required');
}
// For frameworks like NextJS, need to ensure this can only be attempted in the browser.
if (typeof window !== 'undefined') {
let resolvedUrl;
try {
resolvedUrl = new URL(logoutUrl, window.location.origin);
}
catch {
throw new index.WristbandError(authProviderTypes.WristbandErrorCode.INVALID_LOGOUT_URL, `Redirect To Logout: "${logoutUrl}" is not a valid logout URL`);
}
for (const key of reservedLogoutQueryKeys) {
if (resolvedUrl.searchParams.has(key)) {
throw new index.WristbandError(authProviderTypes.WristbandErrorCode.INVALID_LOGOUT_URL, `Redirect To Logout: logoutUrl must not include reserved query param: "${key}"`);
}
}
const queryParams = new URLSearchParams({
...(config.tenantDomain ? { tenant_domain: config.tenantDomain } : {}),
...(config.tenantCustomDomain ? { tenant_custom_domain: config.tenantCustomDomain } : {}),
});
resolvedUrl.searchParams.forEach((value, key) => {
queryParams.append(key, value);
});
resolvedUrl.search = queryParams.toString();
window.location.href = resolvedUrl.toString();
}
}
exports.redirectToLogin = redirectToLogin;
exports.redirectToLogout = redirectToLogout;
;