Authentication That Just Works
No Passport.js. No Auth0. No NextAuth. FLIN has 11 authentication methods as native functions — session management, OAuth with 8 providers, OTP, 2FA, JWT, and bcrypt. All compiled in. All zero-config.
11 Authentication Methods
Every method is a native function. No SDKs, no external services, no API keys for basic auth. Just call the function and authenticate.
Email / Password
Classic login with bcrypt_hash() and bcrypt_verify(). Deliberately slow hashing, auto-salted, resistant to brute-force and rainbow table attacks.
Email OTP
Passwordless login via one-time codes. otp_generate(6) creates the code, send_email() delivers it. Verify and auto-create accounts.
WhatsApp OTP
whatsapp_send_otp(phone) sends a verification code via WhatsApp. Perfect for African and South Asian markets where WhatsApp is the primary channel.
TOTP / 2FA
Two-factor authentication with totp_secret() and totp_verify(). Compatible with Google Authenticator, Authy, and any TOTP app.
JWT Tokens
jwt_encode() and jwt_decode() with configurable expiry and custom claims. Stateless authentication for APIs and microservices.
Session Management
Built-in session.* system with encrypted cookies. Set session.user on login, check in middleware, clear on logout. 1-year expiry, SameSite=Lax.
8 OAuth Providers. Two Functions Each.
Every OAuth provider is a pair of native functions: one generates the login URL, one handles the callback. No SDK installation, no middleware chain.
OAuth 2.0 with PKCE
auth_google_login / callbackGitHub
OAuth 2.0 with PKCE
auth_github_login / callbackDiscord
OAuth 2.0 standard
auth_discord_login / callbackApple
Sign-In with Apple (ES256 JWT)
auth_apple_login / callbackOpenID Connect
auth_linkedin_login / callbackTelegram
HMAC-SHA256 widget verification
verify_telegram_loginClerk
JWT verification + Backend API
verify_clerk_tokenFirebase
JWT claim validation
verify_firebase_tokenSee It in Action
From login form to protected dashboard in three files. No boilerplate, no configuration, no external services.
email = "" password = "" errorKey = "" <h1>Login</h1> {if errorKey != ""} <p class="error">{t(errorKey)}</p> {/if} <input bind={email} placeholder="Email" /> <input type="password" bind={password} placeholder="Password" /> <button click={ if email == "" { errorKey = "error.email_required" } else { session.loginEmail = email session.loginPass = password location.href = "/auth/process-login" } }>Log In</button> <!-- OAuth providers — use <a> tags, not click handlers --> <a href={auth_google_login().url}>Continue with Google</a> <a href={auth_github_login().url}>Continue with GitHub</a>
loginEmail = session.loginEmail || "" loginPass = session.loginPass || "" session.loginPass = none // Clear password IMMEDIATELY loginOk = false fn processLogin() { if loginEmail != "" && loginPass != "" { found = User.where(email == loginEmail).first if found != none { valid = bcrypt_verify(loginPass, found.password) if valid { session.user = found.email session.userName = found.name session.userId = to_text(found.id) loginOk = true } } } } processLogin() session.loginEmail = none {if loginOk} <h2>Welcome!</h2> <script>setTimeout(() => window.location.href = "/dashboard", 1000)</script> {else} <p>Invalid credentials.</p> <a href="/login">Try Again</a> {/if}
middleware { matcher: ["/dashboard/**", "/settings"] exclude: ["/", "/login", "/register", "/auth/**"] if session.user == none { redirect("/login") } next() }
Passwordless Authentication
Email OTP and WhatsApp OTP — send a code, verify it, log in. No passwords to remember, no credentials to store.
Email OTP
Generate a 6-digit code with otp_generate(6), send it via send_email(), verify on the next page. Auto-create accounts for new users.
WhatsApp OTP
whatsapp_send_otp(phone) delivers a code via WhatsApp Business Cloud API. Verify with a simple string comparison. New users complete a profile step.
Security Built In
Codes expire after 10 minutes. Session variables are cleared immediately after reading. Same error messages for "user not found" and "wrong code" to prevent enumeration.
// Step 1: Send OTP via WhatsApp result = whatsapp_send_otp(phone) if result.success { session.waOtpCode = result.code session.waOtpPhone = phone } // Step 2: User enters the code, verify it if otpInput == session.waOtpCode { found = User.where(phone == otpPhone).first if found != none { session.user = found.email // Existing user — log in } else { // New user — redirect to profile completion } }
Registration with File Upload
FLIN's route POST blocks handle multipart forms natively. Accept avatars, validate files, and create accounts — all in one route.
route POST { validate { firstName: text @required @minLength(1) email: text @required @email password: text @required @minLength(8) avatar: file @max_size("5MB") } existing = User.where(email == body.email).first if existing != none { redirect("/register?error=email_taken") } avatarPath = "" if body.avatar != none { avatarPath = save_file(body.avatar, ".flindb/avatars/") } newUser = User { email: body.email, password: bcrypt_hash(body.password), name: body.firstName, avatar: avatarPath, provider: "Email" } save newUser session.user = newUser.email session.userName = newUser.name session.userId = to_text(newUser.id) redirect("/dashboard") }
Password Reset & Account Recovery
Send a reset code via email, verify it, update the password. The full flow is two files and zero third-party services.
Send Reset Code
Generate a 6-digit code, email it to the user. Don't reveal whether the email exists — always show "code sent" to prevent enumeration.
Verify & Reset
Compare codes, find the user, hash the new password with bcrypt_hash(), save. Clear all session variables immediately.
Security Patterns
Codes expire in 10 minutes. Session variables cleared on read. Generic error messages prevent email enumeration. Rate limiting on send endpoints.
The User Entity
A complete user model that supports all 11 auth methods. OAuth users have no password. WhatsApp users authenticate by phone. Every field has a purpose.
enum AuthProvider { Email, Google, GitHub, Discord, Apple, LinkedIn, Telegram, Clerk, Firebase, WhatsApp } enum UserRole { User, Admin, Moderator } entity User { email: text @required @email password: text = "" // Empty for OAuth users name: text = "" phone: text = "" provider: AuthProvider = "Email" providerId: text = "" // OAuth provider's user ID avatar: text = "" emailVerified: bool = false role: UserRole = "User" @unique(email, role) @index(email) @index(provider) } // Auto: id, created_at, updated_at, deleted_at, version
Auth Functions Reference
Every function is native — compiled into the binary. No imports, no packages, no external dependencies.
Passwords
bcrypt_hash(password)bcrypt_verify(plain, hash)
JWT
jwt_encode(payload, secret, ttl)jwt_decode(token, secret)
TOTP / 2FA
totp_secret()totp_verify(secret, code)
OTP
otp_generate(length)whatsapp_send_otp(phone)
send_email(to, subject, body)
Files
save_file(file, directory)
Configuration
Each provider needs only 2-4 environment variables. Email/password auth needs zero external configuration.
# Base URL (required for OAuth callbacks) BASE_URL=https://myapp.com # Google OAuth GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com GOOGLE_CLIENT_SECRET=GOCSPX-your-secret # GitHub OAuth GITHUB_CLIENT_ID=your-client-id GITHUB_CLIENT_SECRET=your-client-secret # SMTP (for Email OTP and Password Reset) SMTP_HOST=smtp.postmarkapp.com SMTP_PORT=587 SMTP_USER=your-token SMTP_PASSWORD=your-token [email protected] # WhatsApp Business Cloud API WHATSAPP_ACCESS_TOKEN=your-access-token WHATSAPP_PHONE_NUMBER_ID=your-phone-id WHATSAPP_OTP_TEMPLATE_NAME=your-template
Auth in Minutes, Not Days
No Passport.js, no Auth0, no NextAuth, no Firebase Auth SDK. Just native FLIN functions. Login form to protected dashboard in three files.
curl -fsSL https://flin.sh | bash