Authentication
Overview
Housr API v2 uses a custom Legacy JWT Guard to authenticate requests. This guard is compatible with JWT tokens issued by the v1 API, allowing the mobile app and web app to authenticate against both APIs with the same token.
How It Works
LegacyJwtGuard
The custom guard is implemented at app/Auth/Guards/LegacyJwtGuard.php. It:
- Extracts a JWT from the request (see token sources below)
- Decodes it using
firebase/php-jwtwith the sharedJWT_KEYsecret (HS256) - Retrieves the user ID from
subordata.idin the payload - Loads the
Usermodel from the database
Token sources (checked in order):
jwtform/query parameter (legacy v1 compatibility)Authorization: Bearer <token>header- Raw
Authorizationheader (without “Bearer ” prefix)
Token expiry: The guard sets JWT::$leeway = PHP_INT_MAX before decoding, which effectively disables expiry checking. This matches v1 behaviour where tokens are long-lived.
Guard Registration
The guard is registered in app/Providers/LegacyAuthServiceProvider.php:
Auth::extend('legacy-jwt', function ($app, $name, $config) {
return new LegacyJwtGuard(
Auth::createUserProvider($config['provider']),
$app->make('request')
);
});And configured in config/auth.php:
'guards' => [
'legacy-jwt' => [
'driver' => 'legacy-jwt',
'provider' => 'users',
],
],Using Auth in Routes
Required authentication
Apply the auth:legacy-jwt middleware to require a valid JWT:
Route::middleware('auth:legacy-jwt')->group(function () {
Route::get('/protected-endpoint', [Controller::class, 'method']);
});Returns 401 if no valid token is provided.
Optional authentication
Use the OptionalLegacyJwtAuth middleware when the endpoint works for both authenticated and unauthenticated users, but returns extra data for logged-in users:
Route::middleware(OptionalLegacyJwtAuth::class)->group(function () {
Route::get('/houses', [HouseController::class, 'index']);
});This middleware (app/Http/Middleware/OptionalLegacyJwtAuth.php):
- Sets the default guard to
legacy-jwt - Attempts to resolve the user from the token
- Allows the request through regardless of auth result
- The controller can then check
$request->user()fornullvs authenticated user
Webhook/service-to-service authentication
For incoming webhooks (e.g., HubSpot), use ValidateAuthorizationHeader middleware. This checks the Authorization header against a shared secret (AUTHORIZATION_KEY env var):
Route::middleware(ValidateAuthorizationHeader::class)->group(function () {
Route::post('/webhook', [WebhookController::class, 'handle']);
});External API Authentication
The External module (Modules/External) implements a separate OAuth-style token system for third-party API clients:
RequireApiKeymiddleware: Validates API key from theApiClientmodelRequireAuthTokenmiddleware: Validates bearer token issued byAuthControllerRequireScopemiddleware: Checks that the authenticated client has the required scope
External clients authenticate via:
- Exchange API key for a token via the auth endpoint
- Use the token as a Bearer token on subsequent requests
- Scoped access controls which endpoints the client can call
Firebase
The app/Services/FirebaseService.php provides Firebase Realtime Database access (not used for auth — that is handled by the v1 API). Firebase credentials are loaded from the FIREBASE_CREDENTIALS env var (JSON string).
app/Services/FirebaseMessagingService.php handles push notifications via Firebase Cloud Messaging.