dreamfactory/df-oauth
DreamFactory OAuth package that adds OAuth-based authentication to DreamFactory APIs, letting clients sign in with external providers and obtain secure access tokens. Integrates with DreamFactory users and roles for controlled API access.
This is not a standalone Laravel package — it is a DreamFactory service package that plugs into a running DreamFactory instance. It ships the oauth service type that lets DreamFactory both consume third-party OAuth providers (Google, GitHub, Facebook, Azure AD, any OpenID Connect IDP) and act as an OAuth2 provider for downstream apps. Treat it as an extension of dreamfactory/df-core, not something you can require into a generic Laravel app and expect to boot.
First steps:
Install inside a DreamFactory project:
composer require dreamfactory/df-oauth
DreamFactory uses package discovery, so the OAuthServiceProvider registers automatically. Verify it appears under Services → Create → OAuth in the admin console.
Look first at these paths (once vendored):
src/Providers/OAuthServiceProvider.php — route registration, migrations, config merge.src/Handlers/ — the per-provider handlers (Facebook, Google, Github, OAuth2, OIDC, Local). This is where 90% of customization lives.config/oauth.php (if present) — default redirect URIs, scopes, token TTLs.database/migrations/ — the oauth_users / provider-mapping tables.Prerequisites before the service will boot:
php artisan passport:keys (DreamFactory relies on Passport for its own token issuance in some flows)..env has APP_URL set to the public DreamFactory host — the OAuth redirect URIs are derived from it and will silently mismatch otherwise.php artisan migrate has been run (DreamFactory bundles this in its df:setup command; don't skip it).First use case to prove it works: add a Local OAuth service, configure the admin (system) role as the client owner, then walk the authorization-code flow with curl against /api/v2/oauth/{service}/authorize and /token. If that round-trip returns a JWT accepted by an API call, the plumbing is correct and you can move on to third-party providers.
Provider configuration via the admin API, not code. The intended workflow is to create one OAuth service per provider through POST /api/v2/system/service with type: "oauth". Each service instance stores its own client_id, client_secret, scopes, and redirect URI in the service table. Avoid hardcoding credentials in config — DreamFactory encrypts service config at rest.
Handling auth code → token exchange. The service exposes:
GET /api/v2/oauth/{service}/authorize?client_id=&redirect_uri=&scope=&state=POST /api/v2/oauth/{service}/token (code and refresh grants)GET /api/v2/oauth/{service}/me — returns the resolved DreamFactory user for a token.Front-end SPA pattern: open a popup to /authorize, capture the code on your callback route, POST it server-side to /token (never from the browser, because the client_secret is server-side), then send the returned access_token as X-DreamFactory-Session-Token (not Authorization: Bearer) on DreamFactory API calls.
Mapping external identities to DreamFactory roles. After token exchange, df-oauth resolves the external email/ID to a DreamFactory user. Use the role mapping on the service definition to assign an app role automatically. If you need custom logic (e.g., derive role from a Google hd claim), extend the appropriate handler:
class MyGoogleHandler extends GoogleHandler
{
public function handleLogin(array $token): array
{
$payload = $this->decodeIdToken($token['id_token']);
$role = str_ends_with($payload['email'], '@corp.com') ? 'corp-user' : 'guest';
return parent::handleLogin($token) + ['role_id' => $role];
}
}
Register the override by binding it in your own service provider against the service's handler interface, or by copying df-oauth into a fork under a new namespace if you don't control upstream.
Local OAuth as an SSO broker. When DreamFactory is the identity provider (Local OAuth), configure your internal apps with it as the IDP. Issue one OAuth "app" per downstream application so token revocation and scope limits stay per-app. This is the most common production pattern — you get a single audit trail through DreamFactory logs.
Machine-to-machine. Use the Password grant for trusted backend services pointed at /token. Because DreamFactory maps the token to a user with an API key, the resulting token has the user's full RBAC permissions — scope the user minimally.
Testing loop. Write integration tests against a real DreamFactory instance with a fixture OAuth app. Mock the third-party IDP's /token and /userinfo with Guzzle's MockHandler. Assert on the session_token returned by DreamFactory, not on the raw OAuth payload, because df-oauth normalizes the response shape.
It won't work without df-core's auth middleware. The routes are registered under DreamFactory's api/v2 prefix and rely on df-core middleware for session resolution. If you see 404s on /oauth/..., you almost certainly installed the package outside a DreamFactory app — this is the single most common misstep.
Version coupling. DreamFactory's major releases bump df-oauth in lockstep. Pin dreamfactory/df-core and dreamfactory/df-oauth to compatible majors (e.g., both 4.x). Check the composer.json require block after every upgrade; a silent df-core bump frequently breaks the service provider's migration loading.
Redirect URI must match byte-for-byte. OAuth providers compare the redirect_uri exactly, including trailing slash and scheme. DreamFactory builds it from APP_URL. If you're behind a proxy, set TrustProxies correctly or the URI will be http while the provider expects https, and you'll get a cryptic invalid_request from the provider with no DreamFactory-side error.
Passport vs. DreamFactory tokens. df-oauth issues session tokens that DreamFactory validates, but some flows delegate to Passport. If token validation fails with 401 despite a fresh login, check that storage/ is writable and Passport keys (oauth-private.key, oauth-public.key) exist. Regenerate with php artisan passport:keys --force (this invalidates all existing sessions — do it in a maintenance window).
state parameter is your CSRF defense — never strip it. The package generates and validates state internally, but if you build a custom handler or front-end, preserve it end-to-end. Many bug reports trace to SPAs that drop state in the callback handler.
Scopes are advisory, not enforced. DreamFactory RBAC is enforced by the assigned role, not the OAuth scope string. Don't rely on scopes for authorization — rely on the mapped role. Treat the scope field as metadata for the provider handshake only.
Debugging: set LOG_CHANNEL=stderr and LOG_LEVEL=debug; the handler logs token exchange bodies but redacts secrets. Inspect the service table for the stored provider config and the oauth_users mapping table (if migrations ran) for identity links. php artisan config:clear && php artisan route:clear after any config change — DreamFactory caches the service registry aggressively.
Extension points (in rough order of durability):
config overrides in the service definition (safest).Avoid for greenfield Laravel. If you're not already running DreamFactory, do not add this package to get "OAuth on Laravel." Use Passport or Sanctum directly — they're first-party, better documented, and you won't fight DreamFactory's service registry to configure anything. df-oauth only earns its keep when DreamFactory's API gateway and RBAC are the actual target.
How can I help you explore Laravel packages today?