Web Form Authentication
Web form authentication is the recommended authentication method for web applications. It provides a user-friendly login interface and session-based authentication that works seamlessly across multiple requests.
Overview
The form authentication scheme uses an HTML web form for the user to enter their username and password credentials and an HTTP POST request to submit credentials to the server for verification.
When authentication succeeds:
- Ioto creates a login session
- A secure cookie is returned to the client's browser
- Subsequent requests that include the cookie are automatically authenticated using the session
- No need to re-enter credentials for each request
How Form Authentication Works
- User navigates to a protected resource
- Web server redirects unauthenticated user to login page
- User enters username and password in HTML form
- Form is submitted via HTTP POST to login URL
- Server validates credentials against database
- On success, server creates session and returns cookie
- User is redirected to originally requested resource or welcome page
- Future requests include the session cookie for automatic authentication
Creating a Login Form
Here is a minimal example login page:
<html>
<head>
<title>Login</title>
</head>
<body>
<h1>Please Log In</h1>
<form name="details" method="post" action="/api/public/login">
<label for="username">Username:</label>
<input type="text" name="username" id="username" required><br/>
<label for="password">Password:</label>
<input type="password" name="password" id="password" required><br/>
<input type="submit" name="submit" value="Login">
</form>
</body>
</html>Key elements:
- method="post" - Credentials are sent via POST (more secure than GET)
- action="/api/public/login" - URL where credentials are submitted
- type="password" - Masks password input
- required - HTML5 validation for mandatory fields
Configuration
Basic Configuration
Configure form authentication in web.json5:
{
auth: {
// Define roles with their abilities (2-level structure)
roles: {
public: [],
user: ['view', 'read'],
admin: ['user', 'edit', 'delete'],
},
// Login endpoint (uses built-in webLoginUser handler)
login: '/api/public/login',
// Logout endpoint (uses built-in webLogoutUser handler)
logout: '/api/public/logout',
}
}The roles define a two-level authorization system where each role name maps to an array of abilities. Roles can include other roles (e.g., admin includes user) to create hierarchical access control.
When auth.login is set, Ioto automatically uses the built-in webLoginUser function to:
- Extract username and password from POST data
- Validate credentials against the database
- Create a session on successful authentication
- Return appropriate response
Built-in Login Handler
The built-in login handler expects POST data with:
- username - User's username
- password - User's password (plain text, transmitted over HTTPS)
On successful authentication:
- HTTP 200 status code
- Session cookie is set
- Response can include redirect URL
On authentication failure:
- HTTP 401 Unauthorized status code
Custom Login Handler
For custom authentication logic, you can implement your own login action handler and leave auth.login unset.
Example Custom Login
static int login(Web *web)
{
cchar *password, *username;
// Extract credentials from POST data
username = webGetVar(web, "username", 0);
password = webGetVar(web, "password", 0);
// Custom validation logic
if (username && password) {
// Your own custom password checking routine
if (customCheckPassword(username, password)) {
// Get user's role from database
cchar *role = getUserRole(username);
// Create session
webLogin(web, username, role);
// Redirect to welcome page
webRedirect(web, 302, "/welcome.html");
return 0;
}
}
// Authentication failed
webRedirect(web, 401, "/login-failed.html");
return 0;
}
// Register the custom login action
webAddAction(NULL, "/api/public/login", login);Custom Authentication Examples
Two-Factor Authentication:
static int loginWith2FA(Web *web)
{
cchar *username = webGetVar(web, "username", 0);
cchar *password = webGetVar(web, "password", 0);
cchar *token = webGetVar(web, "token", 0);
if (validatePassword(username, password) && validate2FAToken(username, token)) {
webLogin(web, username, getUserRole(username));
webRedirect(web, 302, "/dashboard.html");
} else {
webRedirect(web, 401, "/login.html?error=auth");
}
return 0;
}External Authentication Service:
static int loginWithOAuth(Web *web)
{
cchar *token = webGetVar(web, "oauth_token", 0);
// Validate token with external service
UserInfo *info = validateOAuthToken(token);
if (info) {
webLogin(web, info->username, info->role);
webRedirect(web, 302, "/app.html");
} else {
webRedirect(web, 401, "/login.html");
}
return 0;
}Route Configuration
To implement form-based authentication, configure routes to ensure:
- Login and logout pages are publicly accessible
- Public resources (CSS, JS, images) are accessible
- Protected resources require appropriate roles
Example Route Configuration
web: {
routes: [
// Protected API routes
{match: '/api/admin/', role: 'admin'},
{match: '/api/user/', role: 'user'},
// Public API routes (includes login/logout)
{match: '/api/'},
// Protected document routes
{match: '/admin/', role: 'admin'},
{match: '/user/', role: 'user'},
// Public routes (catchall)
{},
],
}Route Access Table
| URL Pattern | Description | Required Role |
|---|---|---|
| /index.html | Home page | none |
| /login.html | Login page | none |
| /css/ | Stylesheets | none |
| /js/ | JavaScript files | none |
| /images/ | Images | none |
| /api/public/login | Login action | none |
| /api/public/logout | Logout action | none |
| /api/user/ | User API actions | user |
| /api/admin/ | Admin API actions | admin |
| /user/ | User documents | user |
| /admin/ | Admin documents | admin |
Logout Handling
Built-in Logout
When auth.logout is configured, Ioto provides automatic logout handling:
auth: {
logout: '/api/public/logout',
}Logout can be triggered by:
- GET request:
<a href="/api/public/logout">Logout</a> - POST request: Form submission to logout URL
The built-in handler:
- Destroys the session
- Clears the session cookie
- Redirects to home page or specified URL
Custom Logout Handler
static int logout(Web *web)
{
// Perform custom cleanup
logUserLogout(webGetUser(web));
// Destroy session
webLogout(web);
// Redirect to login page
webRedirect(web, 302, "/login.html");
return 0;
}
webAddAction(NULL, "/api/public/logout", logout);Session Management
Session Cookies
Ioto uses secure HTTP-only cookies for session management:
- HttpOnly flag prevents JavaScript access
- Secure flag requires HTTPS (in production)
- SameSite protection against CSRF attacks
Session Timeout
Configure session timeout in web.json5:
web: {
session: {
timeout: 1800, // 30 minutes in seconds
}
}Sessions automatically expire after the timeout period of inactivity.
Session Persistence
Sessions are stored in memory by default. For multi-process or distributed deployments, sessions can be stored in:
- Database
- Redis
- Shared memory
Security Best Practices
Always Use HTTPS
CRITICAL: Form authentication must be used over HTTPS/TLS connections.
web: {
ssl: {
certificate: "certs/server.crt",
key: "certs/server.key",
}
}Never transmit passwords over unencrypted HTTP connections.
Password Security
- Use strong password hashing (bcrypt, scrypt, or Argon2)
- Never log or display passwords
- Implement password complexity requirements
- Consider password expiration policies
CSRF Protection
Implement CSRF tokens for form submissions:
<form method="post" action="/api/public/login">
<input type="hidden" name="csrf_token" value="{{csrf_token}}">
<!-- form fields -->
</form>Validate CSRF tokens in your login handler.
Rate Limiting
Implement rate limiting to prevent brute force attacks:
static int login(Web *web)
{
cchar *username = webGetVar(web, "username", 0);
// Check rate limit
if (isRateLimited(webGetClientIP(web), username)) {
webError(web, 429, "Too many login attempts");
return 0;
}
// Continue with authentication...
}Account Lockout
Lock accounts after failed login attempts:
if (!validatePassword(username, password)) {
incrementFailedAttempts(username);
if (getFailedAttempts(username) >= 5) {
lockAccount(username);
}
webRedirect(web, 401, "/login.html?error=auth");
}Enhanced Login Page Example
A production-ready login page with better UX:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - Device Manager</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<div class="login-container">
<h1>Device Manager Login</h1>
<div id="error-message" class="error" style="display:none;">
Invalid username or password
</div>
<form id="login-form" method="post" action="/api/public/login">
<div class="form-group">
<label for="username">Username</label>
<input
type="text"
id="username"
name="username"
required
autofocus
autocomplete="username">
</div>
<div class="form-group">
<label for="password">Password</label>
<input
type="password"
id="password"
name="password"
required
autocomplete="current-password">
</div>
<div class="form-group">
<label>
<input type="checkbox" name="remember" value="1">
Remember me
</label>
</div>
<button type="submit" class="btn-primary">Login</button>
</form>
<div class="links">
<a href="/forgot-password.html">Forgot password?</a>
</div>
</div>
<script>
// Show error message if login failed
if (window.location.search.includes('error=auth')) {
document.getElementById('error-message').style.display = 'block';
}
</script>
</body>
</html>APIs
Key APIs for form authentication:
- webLogin - Create authenticated session
- webLogout - Destroy session
- webGetVar - Get form POST data
- webGetUser - Get current authenticated user
- webRedirect - Redirect after login/logout
- webAddAction - Register action handler
Samples
Complete working examples:
