Security & Authentication

Build secure authentication systems with password hashing, JWT tokens, role-based access control, and session management.

Password Security

Hashing Passwords

Always hash passwords before storing them:

.input('password', 'password', { required: true })
 
.hashPassword('$input.password', 'password_hash')
 
.dbAdd('"users"', {
  email: '$input.email',
  password: '$password_hash',
  created_at: 'now'
}, 'new_user')

The hashPassword operation uses a secure one-way hashing algorithm (bcrypt) that cannot be reversed.

Verifying Passwords

Verify user-provided passwords against stored hashes:

.input('email', 'email', { required: true })
.input('password', 'password', { required: true })
 
.dbGet('"users"', { email: '$input.email' }, 'user')
.precondition('$user != null', 'Invalid credentials')
 
.verifyPassword('$input.password', '$user.password', 'is_valid')
.precondition('$is_valid == true', 'Invalid credentials')

The verifyPassword operation compares the plain text password against the stored hash securely.

JWT Tokens

Creating Tokens

Generate JWT tokens for authenticated sessions:

.createToken({
  user_id: '$user.id',
  email: '$user.email',
  role: '$user.role'
}, 86400, 'auth_token')

Token parameters:

  • Payload - Data to embed in the token (claims)
  • Expiration - Time to live in seconds (86400 = 24 hours)
  • Alias - Variable name to store the token

Token Expiration

Common expiration times:

.createToken({ user_id: '$user.id' }, 3600, 'token')     // 1 hour
.createToken({ user_id: '$user.id' }, 86400, 'token')    // 24 hours
.createToken({ user_id: '$user.id' }, 604800, 'token')   // 7 days
.createToken({ user_id: '$user.id' }, 2592000, 'token')  // 30 days

Requiring Authentication

Protect endpoints with authentication:

const protectedEndpoint = XanoScript.create('users/profile', 'GET')
  .description('Get user profile')
  .requiresAuth('users')  // Requires valid JWT token
  
  .dbGet('"users"', { id: '$auth.id' }, 'user')
  
  .response({
    user: '$user'
  });

When authentication is required:

  • Request must include a valid JWT token in the Authorization header
  • Token data is available via $auth variable
  • $auth.id contains the user ID from the token
  • Token expiration is automatically validated

Verifying Tokens

Manually verify tokens in your logic:

.input('token', 'text', { required: true })
 
.verifyToken('$input.token', 'token_data')
 
.conditional('$token_data.valid == true')
  .then()
  .var('user_id', 'int', '$token_data.user_id')
  .response({ user_id: '$user_id', valid: true })
.else()
  .return({ error: 'Invalid token' })
.endConditional()

Complete Authentication Flow

User Registration

Create a secure registration endpoint:

const register = XanoScript.create('auth/register', 'POST')
  .description('Register new user')
  .input('email', 'email', { required: true })
  .input('password', 'password', { required: true })
  .input('first_name', 'text', { required: true })
  .input('last_name', 'text', { required: true })
  
  // Check if email already exists
  .dbGet('"users"', { email: '$input.email' }, 'existing')
  .precondition('$existing == null', 'Email already registered')
  
  // Hash password
  .hashPassword('$input.password', 'password_hash')
  
  // Create user
  .dbAdd('"users"', {
    email: '$input.email',
    password: '$password_hash',
    first_name: '$input.first_name',
    last_name: '$input.last_name',
    role: 'user',
    created_at: 'now',
    status: 'active'
  }, 'new_user')
  
  // Generate token
  .createToken({
    user_id: '$new_user.id',
    email: '$new_user.email',
    role: '$new_user.role'
  }, 86400, 'auth_token')
  
  .response({
    user: {
      id: '$new_user.id',
      email: '$new_user.email',
      first_name: '$new_user.first_name',
      last_name: '$new_user.last_name'
    },
    token: '$auth_token'
  });

User Login

Create a login endpoint:

const login = XanoScript.create('auth/login', 'POST')
  .description('User login')
  .input('email', 'email', { required: true })
  .input('password', 'password', { required: true })
  
  // Find user
  .dbGet('"users"', { email: '$input.email' }, 'user')
  .precondition('$user != null', 'Invalid credentials')
  .precondition('$user.status == "active"', 'Account is inactive')
  
  // Verify password
  .verifyPassword('$input.password', '$user.password', 'is_valid')
  .precondition('$is_valid == true', 'Invalid credentials')
  
  // Update last login
  .dbEdit('"users"', 
    { id: '$user.id' },
    { last_login: 'now' },
    'updated_user'
  )
  
  // Generate token
  .createToken({
    user_id: '$user.id',
    email: '$user.email',
    role: '$user.role'
  }, 86400, 'auth_token')
  
  .response({
    user: {
      id: '$user.id',
      email: '$user.email',
      first_name: '$user.first_name',
      last_name: '$user.last_name',
      role: '$user.role'
    },
    token: '$auth_token'
  });

Get Current User

Retrieve authenticated user’s profile:

const me = XanoScript.create('auth/me', 'GET')
  .description('Get current user profile')
  .requiresAuth('users')
  
  .dbGet('"users"', { id: '$auth.id' }, 'user')
  .precondition('$user != null', 'User not found')
  
  .response({
    user: {
      id: '$user.id',
      email: '$user.email',
      first_name: '$user.first_name',
      last_name: '$user.last_name',
      role: '$user.role',
      created_at: '$user.created_at'
    }
  });

Logout

Invalidate user sessions:

const logout = XanoScript.create('auth/logout', 'POST')
  .description('User logout')
  .requiresAuth('users')
  
  // Delete user sessions
  .dbDelete('"sessions"', {
    user_id: '$auth.id'
  }, 'deleted_count')
  
  .response({
    message: 'Logged out successfully'
  });

Role-Based Access Control

Checking User Roles

Implement role-based permissions:

const adminEndpoint = XanoScript.create('admin/users', 'GET')
  .description('List all users (admin only)')
  .requiresAuth('users')
  
  .precondition('$auth.role == "admin"', 'Insufficient permissions')
  
  .dbQuery('"users"', {
    pagination: { page: 1, per_page: 50 },
    sort: { field: 'created_at', direction: 'desc' }
  })
  
  .response({
    users: '$results.items',
    total: '$results.itemsTotal'
  });

Multiple Role Check

Allow multiple roles to access an endpoint:

.requiresAuth('users')
 
.var('is_authorized', 'bool', false)
 
.conditional('$auth.role == "admin"')
  .then()
  .var('is_authorized', 'bool', true)
.elseIf('$auth.role == "moderator"')
  .then()
  .var('is_authorized', 'bool', true)
.endConditional()
 
.precondition('$is_authorized == true', 'Insufficient permissions')

Resource Ownership

Ensure users can only access their own resources:

.requiresAuth('users')
.input('order_id', 'int', { required: true })
 
.dbGet('"orders"', { id: '$input.order_id' }, 'order')
.precondition('$order != null', 'Order not found')
 
// Check ownership
.precondition(
  '$order.user_id == $auth.id || $auth.role == "admin"',
  'Insufficient permissions'
)
 
.response({
  order: '$order'
})

Advanced Permissions

Complex Permission Logic

Implement granular permission checks:

const checkPermission = XanoScript.create('auth/check-permission', 'POST')
  .description('Check user permissions for an action')
  .requiresAuth('users')
  .input('resource', 'text', { required: true })
  .input('action', 'text', { required: true })
  
  .var('has_permission', 'bool', false)
  
  // Admin has all permissions
  .conditional('$auth.role == "admin"')
    .then()
    .var('has_permission', 'bool', true)
  
  // Moderators can read and update
  .elseIf('$auth.role == "moderator"')
    .then()
    .conditional('$input.action == "read" || $input.action == "update"')
      .then()
      .var('has_permission', 'bool', true)
    .endConditional()
  
  // Users can only read
  .elseIf('$auth.role == "user" && $input.action == "read"')
    .then()
    .var('has_permission', 'bool', true)
  
  .endConditional()
  
  .response({
    granted: '$has_permission',
    resource: '$input.resource',
    action: '$input.action',
    user_role: '$auth.role'
  });

Permission-Based Content Filtering

Filter data based on user permissions:

.requiresAuth('users')
 
.conditional('$auth.role == "admin"')
  .then()
  // Admins see all records
  .dbQuery('"documents"', {
    pagination: { page: 1, per_page: 50 }
  }, 'documents')
.else()
  // Users see only their own records
  .dbQuery('"documents"', {
    filters: { user_id: '$auth.id' },
    pagination: { page: 1, per_page: 50 }
  }, 'documents')
.endConditional()
 
.response({
  documents: '$documents.items',
  total: '$documents.itemsTotal'
})

Security Best Practices

Password Strength Requirements

Validate password strength before hashing:

.input('password', 'password', { required: true })
 
.precondition(
  'strlen($input.password) >= 8',
  'Password must be at least 8 characters'
)
 
.hashPassword('$input.password', 'password_hash')

Email Verification

Track email verification status:

.dbAdd('"users"', {
  email: '$input.email',
  password: '$password_hash',
  email_verified: false,
  verification_token: '$verification_token',
  created_at: 'now'
}, 'new_user')

Account Status

Implement account activation/deactivation:

.dbGet('"users"', { email: '$input.email' }, 'user')
.precondition('$user != null', 'Invalid credentials')
.precondition('$user.status == "active"', 'Account is inactive')

Rate Limiting

Track login attempts to prevent brute force:

.dbGet('"login_attempts"', {
  email: '$input.email'
}, 'attempts')
 
.conditional('$attempts != null && $attempts.count >= 5')
  .then()
  .return({ error: 'Too many login attempts. Try again later.' })
.endConditional()

Token Refresh

Implement token refresh for long-lived sessions:

const refreshToken = XanoScript.create('auth/refresh', 'POST')
  .description('Refresh authentication token')
  .requiresAuth('users')
  
  .createToken({
    user_id: '$auth.id',
    email: '$auth.email',
    role: '$auth.role'
  }, 86400, 'new_token')
  
  .response({
    token: '$new_token'
  });

Secure Data Handling

Sanitizing Output

Never return sensitive fields:

.dbGet('"users"', { id: '$auth.id' }, 'user')
 
.response({
  user: {
    id: '$user.id',
    email: '$user.email',
    first_name: '$user.first_name',
    last_name: '$user.last_name',
    role: '$user.role'
    // Password hash is NOT included
  }
})

Input Validation

Always validate and sanitize inputs:

.input('email', 'email', { required: true })
 
.filter('$input.email', ['trim', 'lower'], 'clean_email')
 
.dbGet('"users"', { email: '$clean_email' }, 'user')

Environment Variables

Store secrets in environment variables:

.var('jwt_secret', 'text', 'env.JWT_SECRET')
.var('api_key', 'text', 'env.EXTERNAL_API_KEY')

Never hardcode secrets in your endpoints.

Next Steps