Try it out
See Bastion in action.
Use the demo below to evaluate a password with no API key required. Demo requests are rate limited per IP (10/day). To get persistent access, register for a free native key below.
Input
Result
awaiting input
-
score / 4
-
-
Online (throttled)
-
100 guesses/hr - typical login form
Online (unthrottled)
-
10 guesses/sec - no rate limiting
Offline (slow hash)
-
bcrypt/scrypt - 10k guesses/sec
Offline (fast hash)
-
MD5/SHA-1 - 10B guesses/sec
UI Templates
Drop-in integration snippets.
Ready-to-use HTML and JavaScript logic for common authentication forms. Try typing in the forms below to see the logic in action, then grab the code to use in your own projects.
Live demo API usage: - / -
<form id="reg-form">
<label for="reg-email">Email Address</label>
<input type="email" id="reg-email" name="email" autocomplete="email" required>
<div style="display: flex; justify-content: space-between;">
<label for="reg-pwd">Password</label>
<span id="reg-str-text" aria-live="polite"></span>
</div>
<!-- Input Wrap for toggle visibility -->
<div class="input-wrap">
<input type="password" id="reg-pwd" name="password" autocomplete="new-password" required>
<button type="button" class="toggle-vis">show</button>
</div>
<div class="strength-track">
<div id="reg-str-bar" class="strength-fill"></div>
</div>
<div id="reg-breach" class="breach-warn" style="display: none;" aria-live="assertive"></div>
<button type="submit" id="reg-btn">Create Account</button>
</form>
/* Password Toggle Layout */
.input-wrap {
position: relative;
display: block;
}
.input-wrap input {
width: 100%;
box-sizing: border-box;
padding-right: 3.5rem; /* Space for the show button */
}
.toggle-vis {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
color: #888;
cursor: pointer;
}
.toggle-vis:hover {
color: #333;
}
/* Strength Bar */
.strength-track {
height: 4px;
background-color: rgba(100, 100, 100, 0.2);
border-radius: 2px;
margin-bottom: 12px;
}
.strength-fill {
height: 100%;
width: 0%;
background-color: transparent;
border-radius: 2px;
transition: width 0.3s ease, background-color 0.3s ease;
}
/* Breach Alert */
.breach-warn {
color: #dc503c;
background-color: rgba(220, 80, 60, 0.1);
padding: 10px 12px;
border-left: 3px solid #dc503c;
margin-bottom: 12px;
font-size: 0.85rem;
}
// Show/Hide Toggle Logic
document.querySelectorAll('.toggle-vis').forEach(btn => {
btn.addEventListener('click', (e) => {
const input = e.target.previousElementSibling;
const isHidden = input.type === 'password';
input.type = isHidden ? 'text' : 'password';
e.target.textContent = isHidden ? 'hide' : 'show';
});
});
// Password Evaluation Logic
const pwdInput = document.getElementById('reg-pwd');
const strText = document.getElementById('reg-str-text');
const strBar = document.getElementById('reg-str-bar');
const breachWarn = document.getElementById('reg-breach');
const colors = ['#dc503c', '#e0763a', '#e8a838', '#8bbf6e', '#4caf7d'];
let debounceTimer;
pwdInput.addEventListener('input', (e) => {
clearTimeout(debounceTimer);
const password = e.target.value;
if (!password) {
strText.textContent = ''; strBar.style.width = '0%'; breachWarn.style.display = 'none';
pwdInput.style.borderColor = ''; pwdInput.style.opacity = '1';
return;
}
// Set UI to loading state
strText.textContent = 'Checking...';
strText.style.color = '#888';
pwdInput.style.opacity = '0.7';
// Wait 500ms after user stops typing before calling API
debounceTimer = setTimeout(async () => {
try {
const res = await fetch('https://bastion.eande171.workers.dev/v1/evaluate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// SECURITY WARNING: Route this via your backend proxy to hide your API key
'Authorization': 'Bearer YOUR_API_KEY'
},
body: JSON.stringify({ password })
});
const data = await res.json();
// Ensure the user hasn't cleared the input while waiting for the response
if (pwdInput.value !== password) return;
pwdInput.style.opacity = '1';
// Update Strength UI
strText.textContent = data.strength;
strText.style.color = colors[data.score];
strBar.style.width = `${(data.score / 4) * 100}%`;
strBar.style.backgroundColor = colors[data.score];
pwdInput.style.borderColor = colors[data.score];
// Update Breach UI
if (data.breached) {
breachWarn.style.display = 'block';
breachWarn.innerHTML = `⚠ Password found in ${data.breach_count} data breaches.`;
} else {
breachWarn.style.display = 'none';
}
} catch (err) {
pwdInput.style.opacity = '1';
console.error("Evaluation failed", err);
}
}, 500);
});
Live demo API usage: - / -
<form id="login-form">
<label for="login-email">Email Address</label>
<input type="email" id="login-email" name="email" autocomplete="email" required>
<div style="display: flex; justify-content: space-between;">
<label for="login-pwd">Password</label>
<a href="#" style="font-size: 0.8rem;">Forgot?</a>
</div>
<!-- Input Wrap for toggle visibility -->
<div class="input-wrap">
<input type="password" id="login-pwd" name="password" autocomplete="current-password" required>
<button type="button" class="toggle-vis">show</button>
</div>
<div id="login-alert" class="breach-warn" style="display: none;" aria-live="assertive"></div>
<button type="submit" id="login-btn">Sign In</button>
</form>
/* Password Toggle Layout */
.input-wrap {
position: relative;
display: block;
}
.input-wrap input {
width: 100%;
box-sizing: border-box;
padding-right: 3.5rem; /* Space for the show button */
}
.toggle-vis {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
color: #888;
cursor: pointer;
}
.toggle-vis:hover {
color: #333;
}
/* Breach Alert */
.breach-warn {
color: #dc503c;
background-color: rgba(220, 80, 60, 0.1);
padding: 10px 12px;
border-left: 3px solid #dc503c;
margin-bottom: 12px;
font-size: 0.85rem;
}
// Show/Hide Toggle Logic
document.querySelectorAll('.toggle-vis').forEach(btn => {
btn.addEventListener('click', (e) => {
const input = e.target.previousElementSibling;
const isHidden = input.type === 'password';
input.type = isHidden ? 'text' : 'password';
e.target.textContent = isHidden ? 'hide' : 'show';
});
});
// Login Check Logic
const loginForm = document.getElementById('login-form');
const pwdInput = document.getElementById('login-pwd');
const alertBox = document.getElementById('login-alert');
const submitBtn = document.getElementById('login-btn');
loginForm.addEventListener('submit', async (e) => {
e.preventDefault();
const password = pwdInput.value;
if (!password) return;
submitBtn.disabled = true;
submitBtn.textContent = 'Checking...';
pwdInput.style.opacity = '0.7';
alertBox.style.display = 'none';
try {
const res = await fetch('https://bastion.eande171.workers.dev/v1/evaluate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// SECURITY WARNING: Route this via your backend proxy to hide your API key
'Authorization': 'Bearer YOUR_API_KEY'
},
body: JSON.stringify({ password })
});
const data = await res.json();
// Intercept login if password is compromised
if (data.breached) {
alertBox.style.display = 'block';
alertBox.textContent = `⚠ Action Required: This password was compromised in a data breach. You must reset it before proceeding.`;
} else {
// Password is safe, proceed with standard auth
alertBox.style.display = 'none';
console.log("Safe, submitting to backend...");
// loginForm.submit();
}
} catch (err) {
console.error("Evaluation failed", err);
} finally {
pwdInput.style.opacity = '1';
submitBtn.disabled = false;
submitBtn.textContent = 'Sign In';
}
});
Live demo API usage: - / -
<form id="change-password-form">
<label for="cp-current">Current Password</label>
<div class="input-wrap">
<input type="password" id="cp-current" name="current-password" autocomplete="current-password" required>
<button type="button" class="toggle-vis">show</button>
</div>
<div style="display: flex; justify-content: space-between;">
<label for="cp-new">New Password</label>
<span id="cp-str-text" aria-live="polite"></span>
</div>
<div class="input-wrap">
<input type="password" id="cp-new" name="new-password" autocomplete="new-password" required>
<button type="button" class="toggle-vis">show</button>
</div>
<div class="strength-track">
<div id="cp-str-bar" class="strength-fill"></div>
</div>
<div id="cp-breach" class="breach-warn" style="display: none;" aria-live="assertive"></div>
<label for="cp-confirm">Confirm New Password</label>
<div class="input-wrap">
<input type="password" id="cp-confirm" name="confirm-password" autocomplete="new-password" required>
<button type="button" class="toggle-vis">show</button>
</div>
<div id="cp-match" style="display: none; color: red;" aria-live="polite">Passwords do not match</div>
<button type="submit" id="cp-btn">Update Password</button>
</form>
/* Password Toggle Layout */
.input-wrap {
position: relative;
display: block;
}
.input-wrap input {
width: 100%;
box-sizing: border-box;
padding-right: 3.5rem; /* Space for the show button */
}
.toggle-vis {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
color: #888;
cursor: pointer;
}
.toggle-vis:hover {
color: #333;
}
/* Strength Bar */
.strength-track {
height: 4px;
background-color: rgba(100, 100, 100, 0.2);
border-radius: 2px;
margin-bottom: 12px;
}
.strength-fill {
height: 100%;
width: 0%;
background-color: transparent;
border-radius: 2px;
transition: width 0.3s ease, background-color 0.3s ease;
}
/* Breach Alert */
.breach-warn {
color: #dc503c;
background-color: rgba(220, 80, 60, 0.1);
padding: 10px 12px;
border-left: 3px solid #dc503c;
margin-bottom: 12px;
font-size: 0.85rem;
}
// Show/Hide Toggle Logic
document.querySelectorAll('.toggle-vis').forEach(btn => {
btn.addEventListener('click', (e) => {
const input = e.target.previousElementSibling;
const isHidden = input.type === 'password';
input.type = isHidden ? 'text' : 'password';
e.target.textContent = isHidden ? 'hide' : 'show';
});
});
// Update Password Check Logic
const newPwd = document.getElementById('cp-new');
const confPwd = document.getElementById('cp-confirm');
const matchErr = document.getElementById('cp-match');
const strText = document.getElementById('cp-str-text');
const strBar = document.getElementById('cp-str-bar');
const breachWarn = document.getElementById('cp-breach');
const colors = ['#dc503c', '#e0763a', '#e8a838', '#8bbf6e', '#4caf7d'];
let debounceTimer;
// Match checker helper
const checkMatch = () => {
if (confPwd.value && newPwd.value !== confPwd.value) {
matchErr.style.display = 'block';
confPwd.style.borderColor = '#dc503c';
} else {
matchErr.style.display = 'none';
confPwd.style.borderColor = '';
}
};
confPwd.addEventListener('input', checkMatch);
newPwd.addEventListener('input', (e) => {
clearTimeout(debounceTimer);
checkMatch();
const password = e.target.value;
if (!password) {
strText.textContent = ''; strBar.style.width = '0%'; breachWarn.style.display = 'none';
newPwd.style.borderColor = ''; newPwd.style.opacity = '1';
return;
}
// Set UI to loading state
strText.textContent = 'Checking...';
strText.style.color = '#888';
newPwd.style.opacity = '0.7';
debounceTimer = setTimeout(async () => {
try {
const res = await fetch('https://bastion.eande171.workers.dev/v1/evaluate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// SECURITY WARNING: Route this via your backend proxy to hide your API key
'Authorization': 'Bearer YOUR_API_KEY'
},
body: JSON.stringify({ password })
});
const data = await res.json();
// Ensure the user hasn't cleared the input while waiting for the response
if (newPwd.value !== password) return;
newPwd.style.opacity = '1';
strText.textContent = data.strength;
strText.style.color = colors[data.score];
strBar.style.width = `${(data.score / 4) * 100}%`;
strBar.style.backgroundColor = colors[data.score];
newPwd.style.borderColor = colors[data.score];
if (data.breached) {
breachWarn.style.display = 'block';
breachWarn.innerHTML = `⚠ Cannot use breached password.`;
} else {
breachWarn.style.display = 'none';
}
} catch (err) {
newPwd.style.opacity = '1';
}
}, 500);
});
Native key
More than trying it out?
Register for a free native key to get direct API access. No billing required. The free tier gives you 100 requests per day.
Your email is hashed immediately on registration and is used only if you need to regenerate your key. It is never stored in plain text.
Demo
IP rate-limited, no key needed
Free ← you're here
100 req/day, usage controls
RapidAPI
Free and Paid tiers, available now
API Key
-
Regeneration Token
-
Store both values securely. This regeneration token cannot be recovered after this page is closed.