Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/jhonyes04/new-wayfy/llms.txt

Use this file to discover all available pages before exploring further.

WayFy uses JSON Web Tokens (JWT) for stateless authentication across its Flask backend and React frontend. When a user logs in, the backend signs a token containing the user’s identity and role, which the client stores in localStorage and includes as a Bearer header on every subsequent request. The frontend’s AuthContext manages the token lifecycle — validating it on mount, exposing it to components, and clearing it on logout. Backend routes are guarded with Flask-JWT-Extended’s @jwt_required() decorator, and admin-only endpoints additionally verify the is_admin claim embedded in the token.

Auth Flow Overview

1

Register a new account

Send a POST request to /api/users/register with the user’s name, email, and password. No authentication is required for this endpoint.
curl -X POST https://api.wayfy.app/api/users/register \
  -H "Content-Type: application/json" \
  -d '{
    "firstname": "Ada",
    "lastname": "Lovelace",
    "email": "ada@example.com",
    "password": "securepassword123"
  }'
A successful response returns a 201 status with the created user object. The email must be unique — submitting a duplicate email returns a 409 conflict.
2

Log in and receive a token

Send a POST request to /api/users/login with the user’s email and password. No authentication header is required.
curl -X POST https://api.wayfy.app/api/users/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "ada@example.com",
    "password": "securepassword123"
  }'
A successful response includes the signed JWT access token:
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "user": {
    "id": 42,
    "firstname": "Ada",
    "lastname": "Lovelace",
    "email": "ada@example.com",
    "is_admin": false
  }
}
3

Store the token on the client

The frontend stores the token and its expiration timestamp in localStorage under fixed keys:
localStorage.setItem("wayfy_token", token);
localStorage.setItem("wayfy_token_expiration", expirationTimestamp);
These keys are read back on every page load to rehydrate the session without requiring a new login. The AuthContext sets loading: true while this validation runs, preventing protected routes from flashing before the check completes.
4

Include the token in API requests

All authenticated requests must include the token in an Authorization: Bearer header. Use the getAuthHeader(token) helper from apiUtils.js, which also sets Content-Type: application/json:
import { API_BASE_URL, getAuthHeader, handleResponse } from '../services/apiUtils';

const data = await handleResponse(
  await fetch(`${API_BASE_URL}/api/trips`, {
    method: 'POST',
    headers: getAuthHeader(token),
    body: JSON.stringify({ title: 'Barcelona Accessible Trip' }),
  })
);
handleResponse checks response.ok and throws an Error with the server’s body.msg if the request failed, so you can catch meaningful error messages in your UI.
5

Token expiry and logout

Tokens expire after the number of hours set in JWT_ACCESS_TOKEN_EXPIRES_HOURS (default: 1 hour). When the token expires, the backend returns a 401 Unauthorized response. The frontend’s logout() function clears both localStorage keys and resets the AuthContext state, redirecting the user to /login.

JWT Payload Structure

Every token issued by WayFy contains the following claims in its payload. The identity (sub) is stored as a string — the backend issues it via identity=str(user.id) — so get_jwt_identity() always returns a string and must be cast with int() when a numeric comparison is needed.
{
  "sub": "42",
  "email": "ada@example.com",
  "is_admin": false,
  "exp": 1710000000,
  "iat": 1709996400
}
ClaimTypeDescription
substringThe user’s database id as a string. Returned by get_jwt_identity() on the backend. Cast to int when needed: int(get_jwt_identity()).
emailstringThe user’s email address at the time the token was issued.
is_adminbooleanWhether the user has admin privileges. Used to gate admin-only endpoints and UI.
expintegerUnix timestamp when the token expires. Controlled by JWT_ACCESS_TOKEN_EXPIRES_HOURS.
iatintegerUnix timestamp when the token was issued.
The full claims dictionary — including email and is_admin — is accessible on the backend via get_jwt(). The identity alone (user id as a string) is accessible via the lighter get_jwt_identity() call. Use the latter when you only need to look up the user record, and cast to int before integer comparisons or DB lookups.

Backend: Protecting Routes

Requiring Authentication

Decorate any Flask route with @jwt_required() to reject unauthenticated requests with a 401 response:
from flask_jwt_extended import jwt_required, get_jwt_identity, get_jwt

@api.route('/api/trips', methods=['POST'])
@jwt_required()
def create_trip():
    user_id = int(get_jwt_identity())   # string from "sub" claim, cast to int
    # ... create trip for user_id

Admin-Only Endpoints

Read the is_admin claim from the full JWT dictionary to gate admin operations:
from flask_jwt_extended import jwt_required, get_jwt
from flask import jsonify

@api.route('/api/admin/users', methods=['GET'])
@jwt_required()
def list_all_users():
    claims = get_jwt()
    if not claims.get('is_admin', False):
        return jsonify({"msg": "Admin access required"}), 403
    # ... return user list

Direct Bearer Token Requests

For testing or server-to-server calls, pass the token directly in the Authorization header:
curl https://api.wayfy.app/api/trips \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json"

Frontend: AuthContext API

The AuthContext provider wraps the entire application and exposes the following values and methods to any component via useContext(AuthContext):
PropertyTypeDescription
userobject | nullThe decoded user object (id, email, is_admin, etc.) or null when not logged in.
tokenstring | nullThe raw JWT string, ready to pass to getAuthHeader().
loadingbooleantrue while the context is validating a token from localStorage on mount. Render null or a loading spinner while this is true to avoid route flicker.

Usage Example

import { useContext } from 'react';
import { AuthContext } from '../context/AuthContext';

function ProfileButton() {
  const { user, token, loading, logout } = useContext(AuthContext);

  if (loading) return <Spinner />;
  if (!user) return <a href="/login">Sign in</a>;

  return (
    <div>
      <span>Hello, {user.firstname}</span>
      <button onClick={logout}>Log out</button>
    </div>
  );
}

Protected and Admin Routes

WayFy uses two route-guard components to restrict access at the React Router level.

ProtectedRoute

Checks !!user from AuthContext. If the user is not authenticated, it redirects to /login. Wrap any route that requires a logged-in user.
<Route
  path="/trips"
  element={
    <ProtectedRoute>
      <TripsPage />
    </ProtectedRoute>
  }
/>

AdminRoute

Checks user.is_admin. If the flag is false or the user is not logged in, it redirects to /login. Wrap routes that are only accessible to administrators.
<Route
  path="/admin/dashboard"
  element={
    <AdminRoute>
      <AdminDashboard />
    </AdminRoute>
  }
/>
Both ProtectedRoute and AdminRoute wait for loading to be false before evaluating the redirect condition. Never assume user is populated synchronously — always account for the loading state in components that render before the context resolves.

Rate Limiting

WayFy uses Flask-Limiter to protect API endpoints against abuse. Rate limits are applied to the AI assistant routes (e.g. 20–30 requests per minute) and utility routes (60 requests per minute). The /api/users/login and /api/users/register endpoints do not currently have per-route rate limits applied.
When handleResponse receives a 429 status, it throws an Error with the server’s msg. Catch it in your login form and display a user-friendly message:
try {
  await login(email, password);
} catch (err) {
  if (err.message.includes('Too Many Requests')) {
    setError('Too many requests. Please wait a moment and try again.');
  } else {
    setError(err.message);
  }
}

Build docs developers (and LLMs) love