Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Oauth2 Dropbox Laravel Package

stevenmaguire/oauth2-dropbox

A Dropbox OAuth 2.0 provider for the League OAuth 2.0 client. Handles Dropbox authorization URLs, access token exchange, refresh tokens, and resource owner details, so PHP apps can add "Sign in with Dropbox" quickly.

Deep Wiki
Context7

Getting Started

Install the provider alongside the League OAuth 2.0 client (Composer pulls it in):

composer require stevenmaguire/oauth2-dropbox

Before writing code, create a Dropbox app in the App Console:

  1. Choose Scoped access and either App folder or Full Dropbox.
  2. On the Permissions tab, enable every scope you intend to request (e.g. account_info.read, files.metadata.read).
  3. On the Settings tab, add your redirect URI exactly, including scheme, host, path, and trailing slash.

Minimal server-side flow:

use StevenMaguire\OAuth2\Client\Provider\Dropbox;

$provider = new Dropbox([
    'clientId'     => config('services.dropbox.client_id'),
    'clientSecret' => config('services.dropbox.client_secret'),
    'redirectUri'  => route('dropbox.callback'),
]);

// Redirect the user
$authUrl = $provider->getAuthorizationUrl([
    'scope'            => ['account_info.read', 'files.metadata.read'],
    'token_access_type' => 'offline', // needed for a refresh token
]);
session(['dropbox_oauth2state' => $provider->getState()]);

return redirect($authUrl);
// Handle the callback
if (empty($_GET['state']) || $_GET['state'] !== session('dropbox_oauth2state')) {
    abort(403, 'Invalid OAuth state');
}

$token = $provider->getAccessToken('authorization_code', [
    'code' => $_GET['code'],
]);

$owner = $provider->getResourceOwner($token);

$owner->getId();    // Dropbox account_id
$owner->getName();  // display name
$owner->getEmail(); // may be null depending on scopes

The first use case is “Sign in with Dropbox”: exchange the code, persist the token, call users/get_current_account through getResourceOwner(), and key your local user record on the returned account_id.

Where to look first:

  • StevenMaguire\OAuth2\Client\Provider\Dropbox — URLs, scopes, error handling, resource-owner creation.
  • StevenMaguire\OAuth2\Client\Provider\DropboxResourceOwner — which fields are exposed.
  • League’s AbstractProvider, AccessToken, and getAuthenticatedRequest() docs — most day-to-day API work happens there, not in this package.

Implementation Patterns

Bind the provider per request, not as a long-lived singleton. The provider carries OAuth state. A singleton is usually fine under PHP-FPM because the container dies with the request, but under Octane or queue workers you should use a request-scoped binding or instantiate it in the controller.

$this->app->bind(Dropbox::class, fn () => new Dropbox([
    'clientId'     => config('services.dropbox.client_id'),
    'clientSecret' => config('services.dropbox.client_secret'),
    'redirectUri'  => config('services.dropbox.redirect_uri'),
]));

Persist the full token, encrypted. Store access_token, refresh_token, expiry, and the Dropbox account_id. Use Laravel’s encrypted cast; a bearer token is a credential.

$row->update([
    'access_token'      => $token->getToken(),
    'refresh_token'     => $token->getRefreshToken() ?? $row->refresh_token,
    'expires_at'        => $token->getExpires(),
    'dropbox_account_id'=> $owner->getId(),
]);

Rehydrate later with the League token object:

$token = new \League\OAuth2\Client\Token\AccessToken([
    'access_token'  => $row->access_token,
    'refresh_token' => $row->refresh_token,
    'expires'       => $row->expires_at?->timestamp,
]);

Refresh proactively. Dropbox access tokens are short-lived (about four hours). Refresh before expiry rather than reacting to a 401:

if ($token->getExpires() && $token->getExpires() < time() + 60) {
    if (! $token->getRefreshToken()) {
        // force re-authorization
    }

    $new = $provider->getAccessToken('refresh_token', [
        'refresh_token' => $token->getRefreshToken(),
    ]);

    // Dropbox may omit a new refresh token; keep the old one.
    $row->update([
        'access_token'  => $new->getToken(),
        'refresh_token' => $new->getRefreshToken() ?? $token->getRefreshToken(),
        'expires_at'    => $new->getExpires(),
    ]);
}

Use getAuthenticatedRequest() for occasional Dropbox API calls. It applies the bearer header and lets you control method, headers, and body:

$request = $provider->getAuthenticatedRequest(
    'POST',
    'https://api.dropboxapi.com/2/files/list_folder',
    $token,
    [
        'headers' => ['Content-Type' => 'application/json'],
        'body'    => json_encode(['path' => '', 'recursive' => false]),
    ]
);

$data = $provider->getParsedResponse($request);

For heavier Dropbox usage, hand $token->getToken() to dropbox/dropbox-sdk-php and let the SDK handle typed endpoints; keep this provider for the OAuth handshake and identity lookup.

Centralize scopes in config. Scope changes force users to re-consent, so treat the list as part of your app contract. Request the minimum: account_info.read for identity, files.metadata.read for listing, files.content.read only when downloading.

Team apps are manual. If the token response includes team_id in getValues(), you must add the Dropbox-API-Select-User header yourself via the headers option on getAuthenticatedRequest(); the provider does not model team admin flows.

Testing. Inject a mocked Guzzle client with $provider->setHttpClient($client) and assert the generated authorization URL and token request. For feature tests, skip the network and insert an encrypted token row, then test your refresh and API-calling code in isolation.

Laravel Socialite. There is no official Dropbox driver. Either write a small custom Socialite provider, or, more simply, skip Socialite and use this provider directly in a controller. Do not mix Socialite’s flow with this provider’s state handling; you will end up with two competing state values.

Gotchas and Tips

  • The resource-owner endpoint is POST. Dropbox’s users/get_current_account is POST-only. The provider accounts for this in fetchResourceOwnerDetails(); if you subclass and override that method, preserve the POST and the api.dropboxapi.com host or you will get 405/400s.
  • No meaningful default scopes. If you call getAuthorizationUrl() without scope, Dropbox issues a token with no permissions and every API call fails. Always pass an explicit scope array. Scopes must also be enabled in the App Console, otherwise the consent screen errors before your callback ever runs.
  • token_access_type=offline is required for refresh tokens. Without it you get a short-lived access token and no refresh token. It is passed as an option to getAuthorizationUrl() and survives into the query string.
  • Refresh responses may not include a new refresh token. Do not overwrite your stored refresh token with null; fall back to the previous value.
  • Redirect URIs are exact-match. Trailing slashes, http vs https, and ports all matter. Dropbox expects HTTPS for production; use a tunnel for local development if localhost is rejected.
  • Validate state yourself. The provider generates it but does not verify it on callback. Compare against your session and reject mismatches.
  • No built-in PKCE. Dropbox requires PKCE for public clients (SPA/mobile). This package targets confidential server-side clients; if you need PKCE, you must add the verifier/challenge handling around the authorization URL manually.
  • getId() is Dropbox’s account_id. There is no numeric id field. Use account_id as your stable external key.
  • JSON bodies for Dropbox APIs. League’s default POST encoding is form-urlencoded. Set Content-Type: application/json and json_encode() the body yourself. Also remember Dropbox root is "", not "/".
  • Error shapes differ by endpoint. Token errors use error and error_description; API errors use structured JSON with error_summary. IdentityProviderException::getResponseBody() is your friend when debugging.
  • Handle 429s. Dropbox rate limits with Retry-After. The provider does not retry; wrap calls with backoff or use Http::retry() if you bypass the provider.
  • Revoke on disconnect. POST to https://api.dropboxapi.com/2/auth/token/revoke with the bearer token when a user disconnects, then clear your stored credentials.
  • Package maintenance. Last release was 2021 and the package is small (16 stars, MIT). The API surface is stable, but pin league/oauth2-client and verify PHP compatibility in CI. For greenfield projects, the official dropbox/dropbox-sdk-php (WebAuth) is a viable alternative; this package remains the right choice if you are standardizing on League providers.
  • Do not read env() inside provider construction. Under config:cache, env() returns null; always read from config('services.dropbox.*').
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin