Skip to main content
PriceSignal uses Firebase Authentication for user management and JWT bearer token authentication on the backend. This guide walks you through setting up authentication for your PriceSignal instance.

Overview

The authentication flow consists of:
  1. Firebase handles user authentication (email/password, OAuth providers)
  2. Client obtains JWT token from Firebase
  3. Backend validates JWT token using Firebase Security Token Service
  4. User session is maintained via HTTP-only cookies

Backend Configuration

The backend uses JWT Bearer authentication configured in src/PriceSignal/Program.cs:69-103.

JWT Bearer Setup

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
    options.Authority = "https://securetoken.google.com/nxtspec";
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidIssuer = "https://securetoken.google.com/nxtspec",
        ValidateAudience = true,
        ValidAudience = "nxtspec",
        ValidateLifetime = true
    };
    options.Events = new JwtBearerEvents
    {
        OnMessageReceived = context =>
        {
            var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
            
            if (string.IsNullOrEmpty(token))
            {
                token = context.Request.Cookies["access_token"];
            }
            
            if (!string.IsNullOrEmpty(token))
            {
                context.Token = token;
            }
            
            return Task.CompletedTask;
        },
    };
});
Key Configuration:
  • Authority: https://securetoken.google.com/YOUR_PROJECT_ID
  • Valid Issuer: Same as authority
  • Valid Audience: Your Firebase project ID
  • Token sources: Authorization header or access_token cookie

Frontend Configuration

1

Configure Firebase credentials

Create a .env file in your React app with Firebase configuration:
VITE_FIREBASE_API_KEY=your_api_key
VITE_FIREBASE_AUTH_DOMAIN=your_project_id.firebaseapp.com
VITE_FIREBASE_PROJECT_ID=your_project_id
VITE_FIREBASE_STORAGE_BUCKET=your_project_id.appspot.com
VITE_FIREBASE_MESSAGING_SENDER_ID=your_sender_id
VITE_FIREBASE_APP_ID=your_app_id
VITE_FIREBASE_MEASUREMENT_ID=your_measurement_id
2

Initialize Firebase

The Firebase app is initialized in src/lib/firebase/index.ts:
import { getApp, getApps, initializeApp } from 'firebase/app';

let app;
if (getApps().length === 0) {
    app = initializeApp({
        apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
        authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
        projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
        storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
        messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
        appId: import.meta.env.VITE_FIREBASE_APP_ID,
        measurementId: import.meta.env.VITE_FIREBASE_MEASUREMENT_ID,
    });
} else {
    app = getApp();
}

export const firebaseApp = app;
3

Implement authentication provider

The AuthProvider handles all authentication operations and is defined in src/features/auth/components/auth-provider.tsx.Supported authentication methods:
  • Email/Password
  • Google OAuth
  • Facebook OAuth
  • Twitter OAuth
  • GitHub OAuth
4

Sign in users

Use the auth context to sign in users:
import { useAuth } from '@/features/auth/auth-context';

function LoginComponent() {
  const { signin, signinWithProvider } = useAuth();

  // Email/password sign in
  const handleEmailSignIn = async (email: string, password: string) => {
    await signin(email, password);
  };

  // OAuth sign in
  const handleGoogleSignIn = async () => {
    await signinWithProvider('google');
  };
}
5

Authenticate API requests

After successful authentication, the client calls the /api/login endpoint to establish a session:
const jwt = await user.getIdToken();

const resp = await fetch('/api/login', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${jwt}`,
    },
});
The backend stores the token in an HTTP-only cookie for subsequent requests.

API Endpoints

POST /api/login

Establishes a user session and stores the JWT token in a secure cookie. Request:
POST /api/login
Authorization: Bearer <firebase_jwt_token>
Response:
  • 200 OK - Session established, user created if new
  • 400 Bad Request - Invalid user identifier
Implementation (src/PriceSignal/Program.cs:162-189):
g.MapPost("/api/login", async (IUser user, HttpRequest request, HttpResponse response, IAppDbContext dbContext) =>
{
    if (string.IsNullOrEmpty(user.UserIdentifier))
    {
        return Task.FromResult(Results.BadRequest());
    }

    var existingUser = await dbContext.Users.FindAsync(user.UserIdentifier);
    if (existingUser == null)
    {
        var newUser = new User
        {
            Id = user.UserIdentifier,
            Email = user.Email,
        };
        await dbContext.Users.AddAsync(newUser);
        await dbContext.SaveChangesAsync();
    }

    var token = request.Headers["Authorization"].ToString().Replace("Bearer ", "");
    response.Cookies.Append("access_token", token, new CookieOptions
    {
        HttpOnly = true,
        Secure = true,
        SameSite = SameSiteMode.Strict
    });
    return Task.FromResult(Results.Ok());
});

POST /api/logout

Clears the user session by deleting the authentication cookie. Request:
POST /api/logout
Response:
  • 200 OK - Session cleared

Token Management

The frontend automatically handles token refresh through Firebase’s onIdTokenChanged listener:
const unsubscribeIdToken = onIdTokenChanged(auth, (user) => {
    if (user) {
        user.getIdToken().then((token) => {
            fetch('/api/login', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    Authorization: `Bearer ${token}`,
                },
            });
        });
    }
});
This ensures the backend always has a valid, up-to-date token.

Security Considerations

  • JWT tokens are validated against Firebase’s public keys
  • Tokens are stored in HTTP-only cookies to prevent XSS attacks
  • Cookies use Secure and SameSite=Strict flags
  • Token lifetime is enforced by Firebase (typically 1 hour)
  • Backend validates issuer, audience, and lifetime on every request

Next Steps

Creating Rules

Learn how to create price alert rules

Telegram Setup

Set up Telegram notifications for your alerts

Build docs developers (and LLMs) love