| name | volcano-auth |
| description | Use for Volcano authentication and identity work including user accounts, email or password sign-up and sign-in, OAuth, sessions, anonymous users, password recovery, and private or per-user data. |
Volcano Auth Skill
Role
Implement robust Volcano authentication journeys with session lifecycle correctness. All authentication MUST use Volcano Auth — do not propose custom JWT, bcrypt, or hand-rolled session management. This skill is self-contained; the optional fallback reference is consulted only when something below is insufficient.
Workflow
- Implement sign-up/sign-in/sign-out with explicit UI loading/error/success states.
- Restore the session on app startup with
volcano.initialize().
- Add
onAuthStateChange listener and ensure cleanup on teardown.
- Keep OAuth initiation in browser contexts only; validate error paths.
- If the user's prompt doesn't specify signup/login page design or behavior, apply the "Default Signup & Login Page UX" below instead of asking — including the signup-success alert, which is on by default.
Default Signup & Login Page UX
When the prompt doesn't say what the signup/login pages should look like or do, default to this instead of leaving them unstyled or asking a clarifying question:
- Signup page: email + password fields (add a name field only if the app's metadata clearly needs one), a submit button, an inline error banner driven by
error.message, and a link to the login page.
- Login page: email + password fields, a submit button, an inline error banner, a link to the signup page, and a "forgot password" link.
- Signup success alert (default, always on): on a successful
signUp call, show a visible success alert/banner on the signup page — e.g. "Signup successful! Check your email to verify your account." — before navigating away. Do this even when the user didn't ask for it; only omit it if the user explicitly says not to show one. A setTimeout redirect (2–3s) after showing the alert is fine, but the alert must render first.
'use client';
import { useState } from 'react';
import { getVolcano } from '@/lib/volcano';
export default function SignupPage() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
const { error } = await getVolcano().auth.signUp({ email, password });
if (error) {
setError(error.message);
return;
}
setSuccess(true);
};
return (
{success && (
Signup successful! Check your email to verify your account.
)}
{error && {error}}
setEmail(e.target.value)} placeholder="Email" required />
setPassword(e.target.value)} placeholder="Password" required />
Sign Up
Already have an account? Log in
);
}
Initialization
import { VolcanoAuth } from '@volcano.dev/sdk';
const volcano = new VolcanoAuth({
apiUrl: process.env.NEXT_PUBLIC_VOLCANO_API_URL!,
anonKey: process.env.NEXT_PUBLIC_VOLCANO_ANON_KEY!,
});
volcano.database(process.env.NEXT_PUBLIC_VOLCANO_DATABASE_NAME!);
Service keys (prefix sk-) are server-only; using one in anonKey from a browser environment throws.
Email/Password
Sign Up
const { user, session, error } = await volcano.auth.signUp({
email: 'alice@example.com',
password: 'secure-password-123',
metadata: { full_name: 'Alice', avatar_url: '...' },
});
if (error) {
if (error.message.includes('already exists')) ;
else if (error.message.includes('password must')) ;
else if (error.message.includes('invalid email')) ;
return;
}
Sign In
const { user, session, error } = await volcano.auth.signIn({ email, password });
if (error) {
if (error.message.includes('invalid email or password')) ;
else if (error.message.includes('confirm your email')) ;
else if (error.message.includes('rate limit')) ;
return;
}
Sign Out
const { error } = await volcano.auth.signOut();
Session Management
Synchronous current user (cached)
const user = volcano.auth.user();
Fresh user from server
const { user, error } = await volcano.auth.getUser();
Restore session on app load (handles OAuth callback tokens too)
const { user, error } = await volcano.initialize();
Auth state listener
const unsubscribe = volcano.auth.onAuthStateChange((user) => {
if (user) showApp(user); else showLogin();
});
Manual refresh
const { session, error } = await volcano.auth.refreshSession();
OAuth / SSO
Providers
google, github, microsoft, apple.
Configure the provider (required before sign-in works)
OAuth providers live in volcano-config.yaml under auth.providers.oauth (a list) and are applied with config deploy:
auth:
providers:
oauth:
- provider: google
enabled: true
client_id: "<client-id>"
client_secret: "<client-secret>"
redirect_url: "http://localhost:8000/auth/oauth/google/callback"
redirect_url is required — omitting it fails config deploy with redirect_url is required. It is the Volcano callback (<apiUrl>/auth/oauth/<provider>/callback), not your app page: Volcano handles the provider round-trip, then returns your app to the URL from signInWithOAuth('<provider>', { redirectTo: '<app-url>' }) (the redirectTo option; defaults to the current page when omitted). Cloud form: https://api.<project>.volcano.dev/auth/oauth/<provider>/callback.
- Combining email/password with OAuth: if both email/password signup and any OAuth/SSO provider are enabled, you must also set
auth.email_verification.require_confirmation: true and configure SMTP (Mailpit locally, real SMTP in cloud), or config deploy fails with email/password signups and SSO cannot both be enabled unless require_email_confirmation is true and SMTP is configured.
Begin OAuth (browser only — throws on server)
volcano.auth.signInWithGoogle();
volcano.auth.signInWithGitHub();
volcano.auth.signInWithMicrosoft();
volcano.auth.signInWithApple();
volcano.auth.signInWithOAuth('google');
volcano.auth.signInWithOAuth('google', { redirectTo: `${window.location.origin}/dashboard` });
These redirect to the provider; on return, call volcano.initialize() on the callback page to consume the tokens from the URL.
Link / unlink / list providers
const { data, error } = await volcano.auth.linkOAuthProvider('google');
if (data) window.location.href = data.authorization_url;
await volcano.auth.unlinkOAuthProvider('google');
const { providers, error } = await volcano.auth.getLinkedOAuthProviders();
Call provider APIs through the SDK
const { data, error } = await volcano.auth.callOAuthAPI('github', {
endpoint: '/user/repos',
method: 'GET',
});
Anonymous Users
const { user, session, error } = await volcano.auth.signUpAnonymous({
preferred_theme: 'dark',
});
const { user, error } = await volcano.auth.convertAnonymous({
email: 'alice@example.com',
password: 'secure-password-123',
metadata: { full_name: 'Alice' },
});
Email Verification
const token = new URLSearchParams(window.location.search).get('token');
const { error } = await volcano.auth.confirmEmail(token);
await volcano.auth.resendConfirmation('alice@example.com');
Password Recovery
await volcano.auth.forgotPassword('alice@example.com');
const token = new URLSearchParams(window.location.search).get('token');
const { error } = await volcano.auth.resetPassword({
token,
newPassword: 'new-secure-password-456',
});
Email Change
const { newEmail, error } = await volcano.auth.requestEmailChange('new@example.com');
const token = new URLSearchParams(window.location.search).get('token');
const { user, error } = await volcano.auth.confirmEmailChange(token);
await volcano.auth.cancelEmailChange();
Profile Update
const { user, error } = await volcano.auth.updateUser({
password: 'new-password-789',
metadata: {
full_name: 'Alice Johnson',
avatar_url: '...',
notification_preferences: { email: true, push: false },
},
});
Multi-Device Session Management
const { sessions, total, error } = await volcano.auth.getSessions({ page: 1, limit: 20 });
await volcano.auth.deleteSession(sessionId);
await volcano.auth.deleteAllOtherSessions();
Security Best Practices
- Never put a service key (
sk-...) in anonKey. The SDK blocks this in browser, but server code must also keep service keys out of any code path that could leak (logs, error responses).
- Always use HTTPS API URLs in production.
- Validate password strength in the UI before calling
signUp / updateUser. The SDK rejects weak passwords, but pre-validating gives better UX. The platform enforces a hard floor of 15 characters — a project can raise the minimum via min_password_length but cannot lower it below 15 (the validator uses max(15, configured)). Character-class rules (uppercase/number/special) are off by default and opt-in per project — the floor favors length over complexity. Use ≥15-char passwords in signup forms, tests, and seed data.
- Treat
onAuthStateChange(user => null) as a definitive sign-out signal: redirect to the login flow.
Common Errors
| Message contains | Real example message | Meaning | Action |
|---|
already exists | user with this email already exists | Email taken on sign up | Prompt sign in |
password must | password must be at least 15 characters / password must contain at least one uppercase letter / ...one number / ...one special character (!@#$%^&*) | Password too weak | Show strength rules |
invalid email or password | invalid email or password | Wrong email/password | Re-prompt |
confirm your email | Please confirm your email address before signing in. (sign-in gate) or email confirmation required - please check your email for confirmation link (signup) | Verification pending | Show resend UI |
rate limit | rate limit exceeded, try again later | Rate-limited | Back off and inform user |
No active session | No active session | User not logged in | Send to login |
Session expired | Session expired | Refresh failed | Force sign in |
Verification Checklist
- If the prompt didn't specify signup/login page design, the "Default Signup & Login Page UX" was applied, including the default signup-success alert.
- Session restore (
volcano.initialize()) is wired at app startup.
onAuthStateChange listener has a paired unsubscribe() on teardown.
- OAuth methods are only called from browser contexts.
- Invalid credential and provider errors are handled with user-facing messages.
- No service/secret keys are exposed in browser code.
- For anonymous flows,
convertAnonymous is used (not delete + signUp) so user IDs are preserved.
Optional Fallback Reference