Magic Link Authentication
Email-based auth with zero password management. Users enter their email, click a link, and they're logged in. No passwords, no OAuth — this is how the InfiniteOcean admin dashboard authenticates.
1 How It Works
From the user's perspective, magic link auth is the simplest possible login:
- User types their email and clicks "Sign in"
- They get an email with a "Sign in" button
- They click it and land back on your app, already logged in
No password to remember, no 2FA app to open, no OAuth consent screen. Behind the scenes, the click verifies a single-use token and stores an Ocean key in the browser. Your app uses that key for all API calls automatically.
POST /auth/login
?token=xxx
2 Build a Login Page
Here's a complete, working login page. It does everything — shows the email form, sends the magic link, handles the callback when the user clicks the email, and displays the logged-in state:
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<!-- Login form (hidden once logged in) -->
<div id="login-form">
<h2>Sign in</h2>
<input type="email" id="email" placeholder="you@example.com">
<button onclick="sendLink()">Send magic link</button>
<p id="status"></p>
</div>
<!-- Shown after login -->
<div id="logged-in" style="display:none">
<h2>Welcome back!</h2>
<p>Logged in as <strong id="user-email"></strong></p>
<button onclick="logout()">Log out</button>
</div>
<script>
const API = 'https://api.infiniteocean.io';
// 1. Check if already logged in
const savedKey = localStorage.getItem('ocean_key');
if (savedKey) showLoggedIn(savedKey);
// 2. Check if returning from email click
const token = new URLSearchParams(location.search).get('token');
if (token) {
// Clean the URL so the token isn't visible
history.replaceState(null, '', location.pathname);
// Verify the token and get the key
fetch(`${API}/auth/verify?token=${token}`)
.then(r => r.json())
.then(data => {
localStorage.setItem('ocean_key', data.key);
showLoggedIn(data.key);
});
}
// 3. Send the magic link
async function sendLink() {
const email = document.getElementById('email').value;
const res = await fetch(`${API}/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email,
callback_url: location.origin + location.pathname
})
});
if (res.ok) {
document.getElementById('status').textContent =
'Check your email! Click the link to sign in.';
}
}
// 4. Show the logged-in view
async function showLoggedIn(key) {
const res = await fetch(`${API}/auth/me`, {
headers: { 'X-Ocean-Key': key }
});
const me = await res.json();
document.getElementById('user-email').textContent = me.email;
document.getElementById('login-form').style.display = 'none';
document.getElementById('logged-in').style.display = 'block';
}
function logout() {
localStorage.removeItem('ocean_key');
location.reload();
}
</script>
</body>
</html>
That's a fully working auth system. The user never sees or copies a key — it's stored in localStorage automatically and sent with every request via JavaScript.
callback_url is the key. It tells InfiniteOcean where to redirect after the email click. Set it to the current page (location.origin + location.pathname) and the user lands right back where they started, now logged in.
3 Store It as a Drop
The login page above is a complete HTML document. Store it as a drop so InfiniteOcean can serve it directly — no separate hosting needed:
curl -X POST https://api.infiniteocean.io/drop \
-H "Content-Type: application/json" \
-H "X-Ocean-Key: YOUR_KEY" \
-d '{
"route": "my-app",
"key": "login",
"payload": "<!DOCTYPE html><html>...the full HTML from above...</html>"
}'
Now the page is live. Users can visit it through the view server or a custom domain, enter their email, and log in — all served from a single drop.
Do the same for every page in your app. A protected dashboard page, a settings page, a profile page — each is a drop with the auth check from the next step baked in:
# Login page
curl -X POST https://api.infiniteocean.io/drop \
-H "Content-Type: application/json" \
-H "X-Ocean-Key: YOUR_KEY" \
-d '{"route": "my-app", "key": "login", "payload": "...login HTML..."}'
# Dashboard (requires auth)
curl -X POST https://api.infiniteocean.io/drop \
-H "Content-Type: application/json" \
-H "X-Ocean-Key: YOUR_KEY" \
-d '{"route": "my-app", "key": "dashboard", "payload": "...dashboard HTML..."}'
# Settings (requires auth)
curl -X POST https://api.infiniteocean.io/drop \
-H "Content-Type: application/json" \
-H "X-Ocean-Key: YOUR_KEY" \
-d '{"route": "my-app", "key": "settings", "payload": "...settings HTML..."}'
Update to redeploy. To change the login page, just POST a new drop with the same route and key. The entity read returns the latest version instantly — no build step, no cache to clear.
4 Protect Your Pages
Once a user is logged in, their key is in localStorage. Use this pattern on any page that requires authentication:
<script>
const key = localStorage.getItem('ocean_key');
if (!key) {
// Not logged in — redirect to login page
location.href = '/login';
} else {
// Logged in — fetch user data with the key
fetch('https://api.infiniteocean.io/entity/my-app/data/profile', {
headers: { 'X-Ocean-Key': key }
})
.then(r => r.json())
.then(data => {
document.getElementById('content').textContent =
'Hello, ' + data.payload.name;
});
}
</script>
The X-Ocean-Key header is attached by your JavaScript automatically. The user never has to think about it — they just see pages that work when they're logged in and a login prompt when they're not.
5 Custom Branding
Customize the email your users receive by passing app_name and subject:
// In your sendLink function:
body: JSON.stringify({
email,
callback_url: location.origin + location.pathname,
app_name: 'My Cool App',
subject: 'Your login link'
})
The email will say "Sign in to My Cool App" instead of "Sign in to InfiniteOcean", with a small "Authenticated by InfiniteOcean" footer.
6 Under the Hood
The login page above makes two API calls. Here they are as curl commands, useful for debugging or server-to-server auth:
Send the magic link:
curl -X POST https://api.infiniteocean.io/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"callback_url": "https://myapp.com/login"
}'
Response
{"sent": true}
Verify the token (from the email link's ?token= parameter):
curl "https://api.infiniteocean.io/auth/verify?token=TOKEN_FROM_EMAIL"
Response
{"key": "hNsuXe..."}
The token is single-use — it's consumed (invalidated) immediately after verification and cannot be reused.
Use the key for authenticated requests:
# Check identity
curl "https://api.infiniteocean.io/auth/me" \
-H "X-Ocean-Key: hNsuXe..."
# Write data
curl -X POST https://api.infiniteocean.io/drop \
-H "X-Ocean-Key: hNsuXe..." \
-H "Content-Type: application/json" \
-d '{"route": "my-app/data", "key": "profile", "payload": {"name": "Alice"}}'
# Read data
curl "https://api.infiniteocean.io/entity/my-app/data/profile" \
-H "X-Ocean-Key: hNsuXe..."
7 Security Notes
- Tokens are single-use (invalidated after verification)
- Tokens expire after 15 minutes
- Rate limited: 5 login attempts per email per hour
- The Ocean key is a cryptographic public key — treat it like a session token
- Use HTTPS always (InfiniteOcean enforces this)
8 How the Admin Dashboard Uses This
The InfiniteOcean admin dashboard at /admin uses exactly this pattern. When you click "Send magic link", it calls POST /auth/login with callback_url set to /admin. The email contains a link back to /admin?token=xxx.
The page JavaScript detects the token in the URL, calls /auth/verify, stores the key in localStorage, and cleans the URL. The admin endpoints then check if that Ocean key is in the ADMIN_KEYS list. Same flow, same code pattern.