Skip to main content

Session Authentication

Session authentication is designed for browser-based applications where users log in through the FLTR web interface. It uses HTTP-only cookies for secure, stateless authentication.

Overview

Session authentication provides:
  • 15,000 requests/hour rate limit
  • HTTP-only cookies to prevent XSS attacks
  • CSRF protection built-in
  • Automatic session management by the browser
  • Same security as OAuth without the complexity

When to Use Sessions

Best for:
  • Single-page applications (SPAs)
  • Server-rendered web apps
  • Admin dashboards
  • Internal tools
Not recommended for:
  • Mobile apps (use OAuth)
  • Server-to-server integrations (use API keys)
  • Third-party integrations (use OAuth)

Quick Start

1

Enable Session Auth

Configure your application to use credentials:
fetch('https://api.fltr.com/v1/datasets', {
  credentials: 'include'  // Send cookies with request
})
2

Redirect to Login

Send users to the FLTR login page:
window.location.href = 'https://www.tryfltr.com/login?redirect_uri=' +
  encodeURIComponent(window.location.href);
3

Make API Calls

After login, session cookies are automatically included:
const response = await fetch('https://api.fltr.com/v1/datasets', {
  credentials: 'include'
});

Implementation

Frontend (React Example)

import { useEffect, useState } from 'react';

function App() {
  const [datasets, setDatasets] = useState([]);
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  useEffect(() => {
    checkAuth();
  }, []);

  // Check if user is authenticated
  async function checkAuth() {
    try {
      const response = await fetch('https://api.fltr.com/v1/auth/session', {
        credentials: 'include'
      });

      if (response.ok) {
        setIsAuthenticated(true);
        loadDatasets();
      }
    } catch (error) {
      console.error('Not authenticated');
    }
  }

  // Load datasets
  async function loadDatasets() {
    const response = await fetch('https://api.fltr.com/v1/datasets', {
      credentials: 'include'
    });

    const data = await response.json();
    setDatasets(data.datasets);
  }

  // Login redirect
  function login() {
    const redirectUri = encodeURIComponent(window.location.href);
    window.location.href = `https://www.tryfltr.com/login?redirect_uri=${redirectUri}`;
  }

  // Logout
  async function logout() {
    await fetch('https://api.fltr.com/v1/auth/logout', {
      method: 'POST',
      credentials: 'include'
    });

    setIsAuthenticated(false);
    setDatasets([]);
  }

  if (!isAuthenticated) {
    return (
      <div>
        <h1>Welcome to FLTR</h1>
        <button onClick={login}>Login</button>
      </div>
    );
  }

  return (
    <div>
      <h1>My Datasets</h1>
      <button onClick={logout}>Logout</button>
      <ul>
        {datasets.map(dataset => (
          <li key={dataset.id}>{dataset.name}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;

Backend (Express.js Example)

If you need server-side session management:
import express from 'express';
import session from 'express-session';
import RedisStore from 'connect-redis';
import { createClient } from 'redis';

const app = express();

// Redis client for session storage
const redisClient = createClient();
await redisClient.connect();

// Session middleware
app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: true,      // HTTPS only
    httpOnly: true,    // Prevent JS access
    sameSite: 'lax',   // CSRF protection
    maxAge: 24 * 60 * 60 * 1000  // 24 hours
  }
}));

// Proxy requests to FLTR API
app.get('/api/datasets', async (req, res) => {
  if (!req.session.fltrAccessToken) {
    return res.status(401).json({ error: 'Not authenticated' });
  }

  const response = await fetch('https://api.fltr.com/v1/datasets', {
    headers: {
      'Authorization': `Bearer ${req.session.fltrAccessToken}`
    }
  });

  const data = await response.json();
  res.json(data);
});

// Login callback
app.get('/auth/callback', async (req, res) => {
  const { code } = req.query;

  // Exchange code for access token (OAuth flow)
  const tokenResponse = await fetch('https://www.tryfltr.com/oauth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code,
      client_id: process.env.FLTR_CLIENT_ID,
      client_secret: process.env.FLTR_CLIENT_SECRET,
      redirect_uri: 'http://localhost:3000/auth/callback'
    })
  });

  const tokens = await tokenResponse.json();

  // Store in session
  req.session.fltrAccessToken = tokens.access_token;
  req.session.fltrRefreshToken = tokens.refresh_token;

  res.redirect('/');
});

// Logout
app.post('/auth/logout', (req, res) => {
  req.session.destroy();
  res.json({ success: true });
});

app.listen(3000);

CORS Configuration

For browser-based requests, configure CORS properly:

Development

// Allow credentials in development
fetch('https://api.fltr.com/v1/datasets', {
  credentials: 'include',  // Important!
  headers: {
    'Content-Type': 'application/json'
  }
})

Production

Add your domain to FLTR’s allowed origins:
  1. Go to SettingsCORS
  2. Add your production domain: https://yourdomain.com
  3. Save changes
FLTR will send these CORS headers:
Access-Control-Allow-Origin: https://yourdomain.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization

CSRF Protection

Session authentication includes built-in CSRF protection using the double-submit cookie pattern.

How It Works

  1. FLTR sets a CSRF token in a cookie
  2. Your app reads the cookie and sends it in a header
  3. FLTR validates the header matches the cookie

Implementation

// Get CSRF token from cookie
function getCsrfToken() {
  const cookies = document.cookie.split(';');
  for (let cookie of cookies) {
    const [name, value] = cookie.trim().split('=');
    if (name === 'XSRF-TOKEN') {
      return decodeURIComponent(value);
    }
  }
  return null;
}

// Include CSRF token in requests
async function makeRequest(url, options = {}) {
  const csrfToken = getCsrfToken();

  const response = await fetch(url, {
    ...options,
    credentials: 'include',
    headers: {
      ...options.headers,
      'X-XSRF-TOKEN': csrfToken,
      'Content-Type': 'application/json'
    }
  });

  return response;
}

// Usage
await makeRequest('https://api.fltr.com/v1/datasets', {
  method: 'POST',
  body: JSON.stringify({ name: 'New Dataset' })
});

Axios Configuration

Axios automatically handles CSRF tokens:
import axios from 'axios';

const api = axios.create({
  baseURL: 'https://api.fltr.com/v1',
  withCredentials: true,  // Include cookies
  xsrfCookieName: 'XSRF-TOKEN',
  xsrfHeaderName: 'X-XSRF-TOKEN'
});

// Make requests
await api.get('/datasets');
await api.post('/datasets', { name: 'New Dataset' });

Session Management

Check Session Status

async function checkSession() {
  try {
    const response = await fetch('https://api.fltr.com/v1/auth/session', {
      credentials: 'include'
    });

    if (response.ok) {
      const session = await response.json();
      return {
        authenticated: true,
        user: session.user,
        expiresAt: session.expires_at
      };
    }
  } catch (error) {
    return { authenticated: false };
  }
}

Extend Session

Sessions automatically extend on each request. To manually extend:
await fetch('https://api.fltr.com/v1/auth/session/refresh', {
  method: 'POST',
  credentials: 'include'
});

Logout

async function logout() {
  await fetch('https://api.fltr.com/v1/auth/logout', {
    method: 'POST',
    credentials: 'include'
  });

  // Redirect to home page
  window.location.href = '/';
}

Security Best Practices

FLTR sets secure cookie attributes:
Set-Cookie: fltr_session=abc123;
  HttpOnly;           // Prevent JavaScript access
  Secure;             // HTTPS only
  SameSite=Lax;       // CSRF protection
  Max-Age=86400;      // 24 hour expiry
  Path=/;             // Available to all paths

Frontend Security

Do:
  • ✅ Use credentials: 'include' for all API requests
  • ✅ Implement CSRF protection
  • ✅ Use HTTPS in production
  • ✅ Validate session before sensitive operations
  • ✅ Implement logout on idle timeout
Don’t:
  • ❌ Store session data in localStorage
  • ❌ Access cookies from JavaScript
  • ❌ Make API calls without CSRF tokens
  • ❌ Trust client-side session checks alone

Backend Security

Do:
  • ✅ Use httpOnly cookies
  • ✅ Set SameSite=Lax or Strict
  • ✅ Implement session expiry
  • ✅ Validate CSRF tokens
  • ✅ Use secure session stores (Redis)
Don’t:
  • ❌ Store sessions in memory (doesn’t scale)
  • ❌ Use predictable session IDs
  • ❌ Send session cookies over HTTP
  • ❌ Trust client-provided session data

Error Handling

401 Unauthorized

Session expired or invalid:
fetch('https://api.fltr.com/v1/datasets', {
  credentials: 'include'
})
.then(response => {
  if (response.status === 401) {
    // Session expired - redirect to login
    window.location.href = '/login';
  }
  return response.json();
})

403 Forbidden

CSRF token invalid:
if (response.status === 403) {
  const error = await response.json();

  if (error.code === 'csrf_token_invalid') {
    // Retry request with fresh CSRF token
    location.reload();
  }
}

Rate Limiting

Session authentication provides 15,000 requests/hour - the same as OAuth. Check rate limit status:
const response = await fetch('https://api.fltr.com/v1/datasets', {
  credentials: 'include'
});

const rateLimit = {
  limit: response.headers.get('X-RateLimit-Limit'),
  remaining: response.headers.get('X-RateLimit-Remaining'),
  reset: response.headers.get('X-RateLimit-Reset')
};

console.log(`${rateLimit.remaining}/${rateLimit.limit} requests remaining`);

Complete Examples

Next.js App Router

// app/api/datasets/route.ts
import { cookies } from 'next/headers';

export async function GET() {
  const cookieStore = cookies();
  const sessionCookie = cookieStore.get('fltr_session');

  if (!sessionCookie) {
    return Response.json({ error: 'Not authenticated' }, { status: 401 });
  }

  const response = await fetch('https://api.fltr.com/v1/datasets', {
    headers: {
      'Cookie': `fltr_session=${sessionCookie.value}`
    }
  });

  const data = await response.json();
  return Response.json(data);
}

// app/datasets/page.tsx
'use client';

export default function DatasetsPage() {
  const [datasets, setDatasets] = useState([]);

  useEffect(() => {
    fetch('/api/datasets')
      .then(res => res.json())
      .then(data => setDatasets(data.datasets));
  }, []);

  return (
    <div>
      <h1>Datasets</h1>
      <ul>
        {datasets.map(dataset => (
          <li key={dataset.id}>{dataset.name}</li>
        ))}
      </ul>
    </div>
  );
}

Vue.js 3

<template>
  <div>
    <div v-if="!authenticated">
      <button @click="login">Login</button>
    </div>

    <div v-else>
      <button @click="logout">Logout</button>
      <ul>
        <li v-for="dataset in datasets" :key="dataset.id">
          {{ dataset.name }}
        </li>
      </ul>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';

const authenticated = ref(false);
const datasets = ref([]);

async function checkAuth() {
  try {
    const response = await fetch('https://api.fltr.com/v1/auth/session', {
      credentials: 'include'
    });

    if (response.ok) {
      authenticated.value = true;
      loadDatasets();
    }
  } catch (error) {
    console.error('Not authenticated');
  }
}

async function loadDatasets() {
  const response = await fetch('https://api.fltr.com/v1/datasets', {
    credentials: 'include'
  });

  const data = await response.json();
  datasets.value = data.datasets;
}

function login() {
  const redirectUri = encodeURIComponent(window.location.href);
  window.location.href = `https://www.tryfltr.com/login?redirect_uri=${redirectUri}`;
}

async function logout() {
  await fetch('https://api.fltr.com/v1/auth/logout', {
    method: 'POST',
    credentials: 'include'
  });

  authenticated.value = false;
  datasets.value = [];
}

onMounted(checkAuth);
</script>

Troubleshooting

Cookies Not Being Set

Cause: CORS configuration issue Solution:
  1. Ensure credentials: 'include' is set
  2. Add your domain to allowed origins in FLTR settings
  3. Use HTTPS in production (required for secure cookies)

CSRF Token Errors

Cause: Missing or invalid CSRF token Solution:
// Ensure X-XSRF-TOKEN header is sent
headers: {
  'X-XSRF-TOKEN': getCsrfToken()
}

Session Expires Too Quickly

Cause: No activity extending session Solution: Make a lightweight request periodically:
// Extend session every 10 minutes
setInterval(async () => {
  await fetch('https://api.fltr.com/v1/auth/session/ping', {
    credentials: 'include'
  });
}, 10 * 60 * 1000);

Next Steps