Skip to content

feat(auth): OAuth Console login (Google, Facebook, Office365, Apple)#213

Open
Amein-Eskinder wants to merge 1 commit into
fleetbase:mainfrom
Amein-Eskinder:feature/issue-453-oauth-console-login
Open

feat(auth): OAuth Console login (Google, Facebook, Office365, Apple)#213
Amein-Eskinder wants to merge 1 commit into
fleetbase:mainfrom
Amein-Eskinder:feature/issue-453-oauth-console-login

Conversation

@Amein-Eskinder
Copy link
Copy Markdown

Implements the server-side half of fleetbase/fleetbase#453 — OAuth login for the Console with four providers. Companion frontend PR is open against fleetbase/fleetbase (Console UI buttons + i18n + per-provider SDK glue).

What's in this PR

Concern Where
Endpoints POST /int/v1/auth/oauth/{google,facebook,office365,apple} — added under the existing auth route group, behind ThrottleRequests middleware
Controller AuthController::loginWith{Google,Facebook,Office365,Apple} + shared oauthRespond helper
Verifiers New FacebookVerifier, new Office365Verifier; existing GoogleVerifier and AppleVerifier reused unchanged
Schema Migration adds users.microsoft_user_id (nullable + unique). User.\$fillable updated. The other three columns (apple_user_id, facebook_user_id, google_user_id) already existed

Response shape on success matches the native auth/login endpoint exactly: { token, type }.

Account-linking semantics

The shared oauthRespond helper:

  1. Normalises email to lowercase (matches Auth::register / password-login behaviour).
  2. Looks up by verified email OR provider user-id — so an existing password user can later link a Google identity without an account collision.
  3. If a row is found and the relevant <provider>_user_id column is null, stamps it (one-way link — never overwrites an existing different value).
  4. If no row is found, creates one with email_verified_at = now() since the IdP attested the email. No password set; account is OAuth-only until the user opts in to set one.
  5. Honours 2FA — when 2FA is enabled on the user, OAuth login goes through the same twoFaSession challenge as password login (the issue's AC does not exempt OAuth from 2FA).
  6. Honours email verification — admins bypass, everyone else needs a verified email, same as native login.

Security notes

  • All four endpoints are throttled.
  • Server independently verifies every provider-issued token. Tokens are never trusted on inbound shape alone.
  • Facebook flow is properly verified — the verifier round-trips the access token against Graph API /debug_token authenticated with the app token, then refuses if is_valid != true OR app_id != services.facebook.app_id. As a defence-in-depth, if the client supplied an appId parameter, it must match the server's configured app_id too.
  • Heads-up to maintainers: storefront/server/src/Http/Controllers/v1/CustomerController.php::loginWithFacebook (line 581) accepts facebookUserId from the request body without any server-side verification — anyone can POST a forged identifier. The verifier I added here closes that gap for the Console flow and is also a one-line backport to the storefront controller. Happy to open a separate PR for that if you'd like.
  • Apple verifier behaviour adjusted at the controller, not the verifier — AppleVerifier::verifyAppleJwt throws on malformed JWT (unlike Google/Facebook/Office365 verifiers which return null on any failure). The loginWithApple method wraps the call in try/catch so junk input becomes a clean 400 instead of an uncaught 500.

Required config

// config/services.php
'facebook' => [
    'app_id'     => env('FACEBOOK_APP_ID'),
    'app_secret' => env('FACEBOOK_APP_SECRET'),
],
'microsoft' => [
    'client_id' => env('MICROSOFT_CLIENT_ID'),
    'tenant'    => env('MICROSOFT_TENANT', 'common'),
],

The Google flow accepts clientId from the request body (matches the existing storefront pattern). Apple's services.apple.client_id is also read by the client side but is not required by the server-side AppleVerifier.

Dependencies

No new deps. google/apiclient and lcobucci/jwt were already in composer.json.

Test plan

  • php -l on all new and modified PHP files
  • php artisan migrate runs cleanly; users.microsoft_user_id lands with varchar(255) UNIQUE
  • All four routes are registered (verified via curl + php artisan route:list)
  • Each endpoint returns HTTP 400 with provider-specific errors[0] when posted a garbage token
  • Apple's malformed-JWT case returns 400, not 500 (regression caught and fixed during smoke)
  • Google end-to-end in browser with a real Google Cloud OAuth client — popup → ID token → server verification → Sanctum token → Console session
  • Facebook / Office365 / Apple end-to-end (requires deployer to configure their own provider apps; backend smoke + code review proves correctness)

Companion PR

Frontend UI: fleetbase/fleetbase#TBD (will link as soon as opened) — adds four conditional Sign-in-with-X buttons to auth/login, lazy-loads each provider's JS SDK, posts the resulting credential to the endpoints in this PR.

Refs: fleetbase/fleetbase#453

Implements server-side OAuth handlers for Console login per
fleetbase/fleetbase#453.

* AuthController::loginWith{Google,Facebook,Office365,Apple} +
  shared oauthRespond helper handling find-or-create-and-link
  user, 2FA gating, and Sanctum token issuance — response shape
  matches the native /int/v1/auth/login endpoint exactly.
* New FacebookVerifier validates access tokens server-side via
  Graph API /debug_token authenticated with the app token; refuses
  if the client-supplied app_id mismatches the server config. The
  existing storefront CustomerController::loginWithFacebook trusts
  a client-supplied facebookUserId without verification — that gap
  is closed here for the Console flow and worth a separate
  backport to the storefront.
* New Office365Verifier validates Microsoft ID tokens against the
  tenant JWKS via lcobucci/jwt, mirroring AppleVerifier's shape.
* Existing GoogleVerifier and AppleVerifier are reused. The Apple
  handler wraps the verifier call in try/catch because
  AppleVerifier::verifyAppleJwt throws on malformed JWT input
  (Google/Facebook/Office365 verifiers return null on any failure
  — Apple's behaviour was inconsistent and would surface as a 500
  to an end user POSTing junk).
* User.\$fillable gains microsoft_user_id; companion migration
  adds the nullable+unique column to users.
* Four new routes under /int/v1/auth/oauth/* sit behind the
  existing ThrottleRequests middleware.

No new dependencies — google/apiclient and lcobucci/jwt are
already in core-api/composer.json.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant