Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/viet2811/uk-travel-recommendation/llms.txt

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

Authentication in the UK Travel Recommendation app is built on JSON Web Tokens. When a user logs in, the Django REST backend issues a short-lived access token and a longer-lived refresh token. The frontend stores both tokens in expo-secure-store — an encrypted, hardware-backed keychain — so they survive app restarts but are never exposed to JavaScript’s global scope or written to plain AsyncStorage. An Axios response interceptor silently refreshes the access token on any 401 Unauthorized response, keeping users logged in without interruption, and the AuthContext exposes the full auth lifecycle to every component in the tree.

Overview

JWT Access Token

Short-lived token sent as Authorization: Bearer <token> on every authenticated request. Stored in expo-secure-store under the key accessToken.

JWT Refresh Token

Longer-lived token used exclusively to mint new access tokens. Stored in expo-secure-store under the key refreshToken. Never sent to any endpoint other than user/token/refresh.

Automatic Refresh

The Axios response interceptor intercepts every 401 response, attempts a silent token refresh, and retries the original request — fully transparent to calling code.

Session Restoration

On every cold start a useEffect reads accessToken from SecureStore. If a token exists, isAuthenticated is set to true without requiring the user to log in again.

AuthContext API

The AuthContext is the single source of truth for authentication state. It is provided at the root of the app via <AuthProvider> and consumed anywhere in the tree via the useAuth() hook.
// context/AuthContext.tsx
interface AuthContextType {
  isAuthenticated: boolean;
  isLoading: boolean;
  login: (username: string, password: string) => Promise<{ success: boolean; message?: string }>;
  logout: () => Promise<void>;
}
PropertyTypeDescription
isAuthenticatedbooleantrue when a valid access token is present in SecureStore.
isLoadingbooleantrue while the initial SecureStore check is in progress on app open. Prevents a flash of the login screen for returning users.
login(username, password) => Promise<{ success, message? }>Submits credentials to the token endpoint. Returns { success: true } on success or { success: false, message: '...' } on failure.
logout() => Promise<void>Clears all tokens, AsyncStorage entries, and the React Query cache, then sets isAuthenticated to false.

Login Flow

1

User submits credentials

The LoginScreen collects a username and password and calls login(username, password) from useAuth().
2

POST to the token endpoint

login() sends a POST request to /user/token/ with the credentials in the request body:
{
  "username": "alice",
  "password": "s3cur3p@ss"
}
3

Tokens stored in SecureStore

On a 200 response the backend returns both tokens. The context stores them immediately:
await SecureStore.setItemAsync('accessToken', access);
await SecureStore.setItemAsync('refreshToken', refresh);
4

Authentication state updated

isAuthenticated is set to true. React Navigation detects the state change and redirects the user to the Main bottom-tab stack (or the PreferenceStack for new accounts that have not yet completed onboarding).

Automatic Token Refresh

The Axios instance defined in api/axios.ts registers a response interceptor that handles expired access tokens transparently. You never need to call the refresh endpoint manually in feature code.
Request sent


Response received

  401? ──Yes──► Set originalRequest._retry = true
                Read refreshToken from SecureStore

                    POST user/token/refresh

               ┌───────┴──────────┐
             200 OK            Error / 401
               │                  │
        Store new accessToken   logout()
        in SecureStore

        Set Authorization header
        on original request

        Retry original request

        Return response
Key behaviours of the interceptor:
  • The _retry flag on the original request config is set to true before the refresh attempt, preventing an infinite retry loop if the refresh endpoint itself returns 401.
  • If no refreshToken is found in SecureStore at the time of a 401, logout() is called immediately without attempting a refresh.
  • If the refresh call throws, logout() is called to clear all credentials.
  • On a successful refresh, the new access token is stored in SecureStore and the Authorization header is updated on the original request before it is retried.

Session Restoration

When the app is opened after being closed (cold start or background eviction), AuthProvider runs a useEffect that checks SecureStore before rendering any navigation:
useEffect(() => {
  const loadUser = async () => {
    const token = await SecureStore.getItemAsync('accessToken');
    token ?? setIsAuthenticated(true);
    setIsLoading(false);
  };
  loadUser();
}, []);
The session restore logic uses the nullish-coalescing operator (??) rather than an explicit if check. Because ?? evaluates the right-hand side only when the left-hand side is null or undefined, setIsAuthenticated(true) is called when token is null (no stored token), which is the opposite of the intended behaviour. In practice, a user without a stored token will briefly appear authenticated. This is a known bug in the source. The intended behaviour is: if a token exists, set isAuthenticated to true.
While isLoading is true the app renders a splash/loading indicator.

Logout

Calling logout() performs a full local sign-out without a network round-trip:
1

Clear tokens from SecureStore

await SecureStore.deleteItemAsync('accessToken');
await SecureStore.deleteItemAsync('refreshToken');
2

Clear AsyncStorage

await AsyncStorage.clear() removes the geo filter preference and any other AsyncStorage keys written by the app, ensuring the next user (or a re-login by the same user) starts from a clean state.
3

Reset the React Query cache

queryClient.clear() removes all cached server state — recommendations, liked history, search results — preventing stale data from a previous session being briefly visible after a new login.
4

Update authentication state

isAuthenticated is set to false, and React Navigation redirects to the Landing screen.
Logout is entirely client-side. The backend access token is not revoked — it will remain valid until its natural expiry. If you need immediate server-side revocation (e.g. after a security incident), implement a token denylist on the backend.

Using the Hook

Any component that needs to react to auth state or trigger auth actions should use the useAuth() hook:
import { useAuth } from 'context/AuthContext';

const { isAuthenticated, login, logout } = useAuth();

// Trigger login from a form submit handler
const handleLogin = async () => {
  const result = await login('alice', 's3cur3p@ss');
  if (!result.success) {
    console.error(result.message); // e.g. "Invalid username or password"
  }
};

// Trigger logout from a settings button
const handleLogout = async () => {
  await logout();
};
useAuth() must be called inside a component that is a descendant of <AuthProvider>. Calling it outside the provider tree throws a runtime error: useAuth must be used within AuthProvider. Ensure <AuthProvider> wraps the root navigator in App.tsx (or _layout.tsx).

Build docs developers (and LLMs) love