Skip to content

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:

  1. Ioto creates a login session
  2. A secure cookie is returned to the client's browser
  3. Subsequent requests that include the cookie are automatically authenticated using the session
  4. No need to re-enter credentials for each request

How Form Authentication Works

  1. User navigates to a protected resource
  2. Web server redirects unauthenticated user to login page
  3. User enters username and password in HTML form
  4. Form is submitted via HTTP POST to login URL
  5. Server validates credentials against database
  6. On success, server creates session and returns cookie
  7. User is redirected to originally requested resource or welcome page
  8. Future requests include the session cookie for automatic authentication

Creating a Login Form

Here is a minimal example login page:

html
<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:

js
{
    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

c
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:

c
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:

c
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:

  1. Login and logout pages are publicly accessible
  2. Public resources (CSS, JS, images) are accessible
  3. Protected resources require appropriate roles

Example Route Configuration

js
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 PatternDescriptionRequired Role
/index.htmlHome pagenone
/login.htmlLogin pagenone
/css/Stylesheetsnone
/js/JavaScript filesnone
/images/Imagesnone
/api/public/loginLogin actionnone
/api/public/logoutLogout actionnone
/api/user/User API actionsuser
/api/admin/Admin API actionsadmin
/user/User documentsuser
/admin/Admin documentsadmin

Logout Handling

Built-in Logout

When auth.logout is configured, Ioto provides automatic logout handling:

js
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

c
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:

js
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.

js
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:

html
<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:

c
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:

c
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:

html
<!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:

Samples

Complete working examples:

See Also