ArtisanPack UI
DocsGoogleScopes

Scopes

Google v1.0.0

Google OAuth uses scopes to gate what your access token can do. lets any installed service package contribute the scopes it needs and unions them into a...

Google OAuth uses scopes to gate what your access token can do. artisanpack-ui/google lets any installed service package contribute the scopes it needs and unions them into a single consent screen — the user only sees one prompt, no matter how many packages depend on Google APIs.

The registry

ArtisanPackUI\Google\Scopes\ScopeRegistry collects three sources:

  1. The baseline — always required to identify the connecting user:
    • openid
    • https://www.googleapis.com/auth/userinfo.email
    • https://www.googleapis.com/auth/userinfo.profile
  2. Filter-hook contributions via ap.google.scopes.
  3. Imperative registrations via ScopeRegistry::register( $scope ).

ScopeRegistry::all() returns the de-duplicated, trimmed union. Empty strings are dropped, and duplicates across the three sources are collapsed.

Registering scopes from a service package

Preferred: hook the ap.google.scopes filter in your service package's boot() method:

use ArtisanPackUI\Hooks\Facades\Filter;

class AnalyticsGoogleServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Filter::add( 'ap.google.scopes', function ( array $scopes ): array {
            $scopes[] = 'https://www.googleapis.com/auth/analytics.readonly';
            return $scopes;
        } );
    }
}

The registry calls Filter::apply('ap.google.scopes', []) inside all(), so every hooked callback contributes to the union. The order the callbacks fire doesn't matter — the union is de-duplicated at the end.

Registering scopes from application code

For app-level scopes without a service provider:

use ArtisanPackUI\Google\Facades\Google;

Google::scopes()->register( 'https://www.googleapis.com/auth/gmail.send' );

Register from anywhere runs before an authorize URL is built — usually inside a service provider's boot().

Reading the current union

use ArtisanPackUI\Google\Facades\Google;

$scopes = Google::scopes()->all();
// ['openid', 'https://www.googleapis.com/auth/userinfo.email', ...]

When a service package is installed after a user is already connected, the registry starts returning scopes the connection doesn't hold. Two methods help you detect this:

$granted = $connection->grantedScopes();

Google::scopes()->missing( $granted );          // ['https://www.googleapis.com/auth/new-scope']
Google::scopes()->hasAllRequired( $granted );   // false

The ConnectionState view model exposes this pre-computed as missingScopes and needsReauthorize, so most callers won't touch the registry directly.

See OAuth/Reauthorize for how to build the incremental-consent URL from a granted-scope list.

Scope hygiene

Google's OAuth policies are strict about scopes:

  • Only request what you use. Adding scopes you don't need triggers a verification review for external apps.
  • Consent screen must list every scope. If a scope isn't in the "OAuth consent screen → Scopes" list on the Cloud Console, Google rejects the authorize request with invalid_scope at runtime. Add it there whenever a service package needs one.
  • Sensitive and restricted scopes require verification. External apps that use scopes marked "sensitive" or "restricted" must complete Google's verification review before non-test-user accounts can consent.

Common scopes by service package

Service package Scope(s) it registers
artisanpack-ui/analytics-google https://www.googleapis.com/auth/analytics.readonly
artisanpack-ui/google-search-console https://www.googleapis.com/auth/webmasters.readonly
artisanpack-ui/google-tag-manager https://www.googleapis.com/auth/tagmanager.readonly, https://www.googleapis.com/auth/tagmanager.edit.containers

Refer to each service package's docs for the authoritative list.

Overriding scopes for a specific flow

OAuthManager::authorizationUrl() accepts an optional scope override:

$url = Google::oauth()->authorizationUrl(
    $user->id,
    override: [
        'openid',
        'https://www.googleapis.com/auth/analytics.readonly',
    ],
);

Useful for a "connect just for analytics" one-off flow that shouldn't require every service package's scopes. In practice this is rarely needed — the union model gets you one consent screen per user, which is almost always what you want.