artisanpack-ui / google
Shared Google OAuth2 authentication, token storage/refresh, and scope management that powers ArtisanPack UI's Google service integrations.
Requires
- php: ^8.2
- artisanpack-ui/core: ^1.0
- artisanpack-ui/hooks: ^1.2
- guzzlehttp/guzzle: ^7.5
- illuminate/database: ^11.0|^12.0|^13.0
- illuminate/encryption: ^11.0|^12.0|^13.0
- illuminate/http: ^11.0|^12.0|^13.0
- illuminate/routing: ^11.0|^12.0|^13.0
- illuminate/support: ^11.0|^12.0|^13.0
Requires (Dev)
- artisanpack-ui/code-style: ^1.1
- artisanpack-ui/code-style-pint: ^1.1
- dealerdirect/phpcodesniffer-composer-installer: ^1.0
- friendsofphp/php-cs-fixer: ^3.75
- laravel/pint: ^1.26
- livewire/livewire: ^3.6
- orchestra/testbench: ^10.2|^11.0
- pestphp/pest: ^3.8|^4.0
- pestphp/pest-plugin-laravel: ^3.2|^4.1
Suggests
- artisanpack-ui/cms-framework: Enables the 'cms' configuration driver to store OAuth credentials via the Settings module.
- livewire/livewire: Enables the <livewire:google-connection-manager /> connection UI (^3.6).
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-03 15:57:18 UTC
README
Shared Google OAuth2 authentication, token storage/refresh, and scope management that powers ArtisanPack UI's Google service integrations. Provider packages like artisanpack-ui/analytics-google, artisanpack-ui/google-search-console, and artisanpack-ui/google-tag-manager sit on top of this package.
- What this package does
- Installation
- Google Cloud Console setup
- Credential storage: config vs. database vs. CMS
- Broker mode
- Connecting a user
- Registering scopes from a service package
- Incremental consent
- Connection-management UI
- Making API calls
- Stateless OAuth primitives
- Configuration reference
- Contributing
What this package does
artisanpack-ui/google is the shared plumbing that every ArtisanPack UI Google integration sits on top of. It owns:
- The OAuth2 authorization-code + PKCE flow — building the consent URL, exchanging the callback code, extracting the user's Google identity from the returned
id_token. - Encrypted token storage on a per-user
google_connectionsmodel, with transparent refresh via the token manager. - A scope registry that lets any installed service package contribute the scopes it needs. Consent covers the union so users only see one screen.
- Credential storage drivers (config file, database, or CMS Settings) so credentials can live wherever a project already stores its secrets.
- An optional broker mode that runs the OAuth flow through an OAuth broker, so a site never holds a Google client secret.
- Stateless OAuth primitives (
Google::client()) for exchanging and refreshing tokens without touching the session or database.
Service packages (Analytics, Search Console, Tag Manager, …) declare the scopes they need and, once a user has connected, call Google::tokens()->getValidAccessToken( $connection ) to make authenticated API calls. They never handle OAuth themselves.
Installation
composer require artisanpack-ui/google
The service provider and Google facade are auto-discovered.
Publish and run migrations to create the google_configurations and google_connections tables:
php artisan vendor:publish --tag=google-migrations php artisan migrate
Optionally publish the config file to customize routes, drivers, or endpoints:
php artisan vendor:publish --tag=google-config
Optional peer packages
| Package | What it enables |
|---|---|
livewire/livewire ^3.6 |
The <livewire:google-connection-manager /> connect/disconnect/status UI. |
artisanpack-ui/cms-framework |
The cms credential driver — stores credentials via the CMS Settings module. |
Neither is required; the base package boots and works without them.
Google Cloud Console setup
- Open the Google Cloud Console and create (or select) a project.
- Navigate to APIs & Services → OAuth consent screen. Choose External unless every user has a Workspace account on the same domain, then fill in the app name, support email, and developer contact.
- Under Scopes, add every scope your installed service packages require. Consent screens for external apps in "Testing" mode are limited to added test users; publish the app when you're ready for production traffic.
- Enable the APIs your service packages depend on (Analytics Data API, Search Console API, Tag Manager API, …) under APIs & Services → Library.
- Go to APIs & Services → Credentials → Create Credentials → OAuth client ID.
- Application type: Web application
- Authorized redirect URI:
https://your-app.test/google/auth/callback(adjust prefix if you changegoogle.routes.prefix).
- Copy the generated Client ID and Client secret.
- Save them into whichever credential driver you've chosen (see below).
Credential storage
artisanpack-ui/google supports three credential storage drivers. Choose one by setting GOOGLE_CONFIG_DRIVER (or config('google.driver')).
config (default)
Reads credentials from config/google.php / .env. Best for single-tenant apps where credentials belong in the deploy pipeline:
GOOGLE_CONFIG_DRIVER=config GOOGLE_CLIENT_ID=your-client-id GOOGLE_CLIENT_SECRET=your-client-secret GOOGLE_REDIRECT_URI=https://your-app.test/google/auth/callback
The config driver is read-only; Google::config()->save() throws.
database
Stores credentials in the google_configurations table. The client secret is encrypted with Laravel's Encrypter (APP_KEY) before it is written. Good for multi-tenant apps or admin-UI-managed credentials:
GOOGLE_CONFIG_DRIVER=database
Google::config()->save( [ 'client_id' => 'your-client-id', 'client_secret' => 'your-client-secret', 'redirect_uri' => 'https://your-app.test/google/auth/callback', ] );
If you rotate APP_KEY without re-encrypting the stored secret, the driver logs a warning and treats the row as unconfigured.
cms (optional, requires artisanpack-ui/cms-framework)
Delegates get/set to the CMS framework's Settings module, so Google credentials live alongside every other site-level setting:
GOOGLE_CONFIG_DRIVER=cms
The client secret is encrypted before it is passed to apUpdateSetting(). Reading and writing goes through apGetSetting() / apUpdateSetting(), so any Settings API a project already exposes can manage these credentials. If the CMS framework is not installed, the driver's setting keys are simply never registered — you get a clear "not configured" state instead of a hard boot error.
Broker mode
Since 1.2.0 the package can run connect, callback and refresh through an OAuth broker instead of talking to Google directly. The site holds only the broker URL, its site ID and its site secret — never a Google client secret:
GOOGLE_OAUTH_MODE=broker GOOGLE_BROKER_URL=https://broker.example.com GOOGLE_BROKER_SITE_ID=site_123 GOOGLE_BROKER_SITE_SECRET=42|plain-secret-from-the-broker
Routes, the connection model, and getValidAccessToken() work unchanged. The broker URL must be HTTPS. Credentials can also be supplied at runtime via the ap.google.broker.credentials filter. If the broker refuses a refresh because the site license has lapsed, the token manager throws LicenseExpiredException (with getRenewUrl()) and leaves the connection connected. See docs/broker-mode.md.
Connecting a user
The base package ships two web routes (mounted under google.routes.prefix, default /google/auth) and two more added for incremental consent and disconnection:
| Route | Method | Name |
|---|---|---|
/connect |
GET | google.auth.connect |
/callback |
GET | google.auth.callback |
/reauthorize |
GET | google.auth.reauthorize |
/disconnect |
POST | google.auth.disconnect |
Send an authenticated user to route('google.auth.connect'):
<a href="{{ route('google.auth.connect') }}">Connect Google</a>
The controller redirects them to Google's consent screen, they return to /callback, the code is exchanged, and a GoogleConnection row is written for the authenticated user. Redirects afterwards honor google.routes.redirect_after_connect and google.routes.redirect_after_error.
Registering scopes from a service package
Service packages contribute scopes via the ap.google.scopes filter hook (from artisanpack-ui/hooks):
use ArtisanPackUI\Hooks\Facades\Filter; // In your service provider's boot() method: Filter::add( 'ap.google.scopes', function ( array $scopes ): array { $scopes[] = 'https://www.googleapis.com/auth/analytics.readonly'; return $scopes; } );
At consent time the base package unions every contributed scope with the baseline (openid, userinfo.email, userinfo.profile) and de-duplicates. A single consent screen covers every dependent service — users don't get a per-package prompt.
Applications that need to add a scope without a service provider can call Google::scopes()->register( $scope ) at runtime instead.
Incremental consent
When a new service package is installed after the account is already connected, the scope registry starts returning scopes the connection doesn't hold. The connection-management component surfaces a Reauthorize action; the google.auth.reauthorize route hits OAuthManager::reauthorizationUrl() and asks Google for only the missing scopes with include_granted_scopes=true — Google merges the new grant with the existing one so the user isn't reprompted for scopes they've already approved.
If your app has its own UI, call the manager directly:
$granted = $connection->grantedScopes(); $url = Google::oauth()->reauthorizationUrl( $userId, $granted );
Connection-management UI
The package ships three interchangeable connection-management components — one Livewire, one React, one Vue — all backed by the same routes and JSON status endpoint. Each shows:
- A Connect Google button when the user is disconnected.
- Connected as {email}, plus Disconnect and Reauthorize actions, when connected.
- A prompt to reauthorize when the scope registry grows (see incremental consent above).
- The scopes that will be requested (or that have been granted) in a collapsible details block.
Scope-limited to connect / disconnect / status by design — no per-service enable/disable toggles.
Livewire
If livewire/livewire is installed, mount:
<livewire:google-connection-manager />
React
The React and Vue components ship as source under resources/js/ — no prebuilt npm package. Either publish the assets into your app's tree:
php artisan vendor:publish --tag=google-js
…or point your bundler at the package directly. Then import:
import { GoogleConnectionManager } from '../vendor/google/react'; // or, if you kept the files inside vendor/ and Vite is configured to see them: // import { GoogleConnectionManager } from '@artisanpack-ui/google/react'; export function IntegrationsPage() { return ( <GoogleConnectionManager onDisconnected={ () => console.log( 'disconnected' ) } /> ); }
Props (all optional):
| Prop | Type | Default | Purpose |
|---|---|---|---|
statusUrl |
string |
/google/auth/status |
Override the status endpoint if you've changed google.routes.prefix. |
csrfToken |
string | null |
Read from <meta name="csrf-token"> |
Sent as X-CSRF-TOKEN on the disconnect POST. |
onDisconnected |
() => void |
Fires after a successful disconnect. | |
onStatusChanged |
(status) => void |
Fires whenever the status payload refreshes. | |
className |
string |
Appended to the root element's class. |
Vue 3
<script setup lang="ts"> import { GoogleConnectionManager } from '../vendor/google/vue'; </script> <template> <GoogleConnectionManager @disconnected="onDisconnected" /> </template>
Props and emits mirror the React component; see the source for the exact shape.
Building your own
The React and Vue components are a convenience. Any UI framework can consume:
GET /google/auth/status→ JSON status payload.GET /google/auth/connect→ redirect to Google.GET /google/auth/reauthorize→ redirect for incremental consent.POST /google/auth/disconnect→ mark disconnected (send the CSRF token).
The same status payload the JS components consume is what the shared resources/js/shared/api.ts module type-encodes — use it as a reference for your own client.
Making API calls
Service packages retrieve a valid access token via the token manager. It refreshes transparently when the current token is within 60 seconds of expiring, and marks the connection disconnected on invalid_grant:
use ArtisanPackUI\Google\Facades\Google; $connection = $user->googleConnection; // or however you load it $token = Google::tokens()->getValidAccessToken( $connection ); Http::withToken( $token ) ->get( 'https://analyticsdata.googleapis.com/v1beta/...' );
Stateless OAuth primitives
Google::client() returns a GoogleClient that performs the Google OAuth calls without the session or database — useful for building an OAuth broker or storing tokens yourself:
use ArtisanPackUI\Google\OAuth\GoogleCredentials; $client = Google::client( new GoogleCredentials( $clientId, $clientSecret, $redirectUri ) ); $url = $client->authorizationUrl( $state, $scopes, [], $verifier ); $tokens = $client->exchangeCode( $code, $verifier ); // TokenResponse $tokens = $client->refresh( $refreshToken ); // TokenResponse $tokens->toArray(); // broker wire shape
Configuration reference
Key options in config/google.php:
| Key | Default | Meaning |
|---|---|---|
client_id / client_secret / redirect_uri |
env(...) |
Credentials used by the config driver. |
driver |
config |
Credential driver: config, database, or cms. |
mode |
direct |
direct or broker. |
broker.url / broker.site_id / broker.site_secret |
env(...) |
OAuth broker credentials (broker mode). |
broker.return_url |
env(...) → callback route |
Where the broker sends the browser back. |
endpoints.authorize |
Google auth URL | Overridable for testing. |
endpoints.token |
Google token URL | Overridable for testing. |
routes.enabled |
true |
Set false to skip the built-in web routes. |
routes.prefix |
google/auth |
Prefix for the five package routes. |
routes.middleware |
['web'] |
Middleware applied to the built-in routes. |
routes.redirect_after_connect |
/ |
Path or route name to send users to after connect/disconnect. |
routes.redirect_after_error |
/ |
Path or route name for OAuth errors. |
user_model |
App\Models\User |
The user model GoogleConnection belongs to. |
Contributing
Please read through the contributing guidelines to learn more about how you can contribute to this project.