PHPackages                             impression/laravel-google-passport-oauth - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. impression/laravel-google-passport-oauth

ActiveLibrary[Authentication &amp; Authorization](/categories/authentication)

impression/laravel-google-passport-oauth
========================================

Google OAuth token exchange for Passport personal access tokens

v1.0.0(1mo ago)00[1 issues](https://github.com/JamesImpression/Laravel-Google-Passport-OAuth/issues)MITPHPPHP ^8.1

Since Apr 28Pushed 1mo agoCompare

[ Source](https://github.com/JamesImpression/Laravel-Google-Passport-OAuth)[ Packagist](https://packagist.org/packages/impression/laravel-google-passport-oauth)[ RSS](/packages/impression-laravel-google-passport-oauth/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (7)Versions (2)Used By (0)

Laravel Google Passport OAuth
=============================

[](#laravel-google-passport-oauth)

Exchange Google OAuth access tokens for Laravel Passport personal access tokens. Enables seamless authentication from external applications (like better-chatbot) to your Laravel MCP server.

Features
--------

[](#features)

- ✅ Validate Google OAuth tokens
- ✅ Refresh expired tokens server-side
- ✅ Find existing users by email (pre-provisioning required)
- ✅ Issue Passport personal access tokens
- ✅ Configurable per-app (column mapping, domain whitelist)
- ✅ No auto-user-creation (prevents account enumeration)
- ✅ Rate limiting built-in (60 requests/minute)
- ✅ Comprehensive error handling with specific status codes
- ✅ Full test coverage (18 passing tests)

Installation
------------

[](#installation)

### Step 1: Install via Composer

[](#step-1-install-via-composer)

```
composer require impression/laravel-google-passport-oauth
```

### Step 2: Publish Configuration

[](#step-2-publish-configuration)

```
php artisan vendor:publish --provider="Impression\GooglePassportOAuth\GoogleOAuthServiceProvider" --tag="google-oauth-config"
```

This creates `config/google-oauth.php` with sensible defaults.

### Step 3: Set Environment Variables

[](#step-3-set-environment-variables)

Add to `.env`:

```
GOOGLE_OAUTH_CLIENT_ID=your-google-client-id-from-console
GOOGLE_OAUTH_CLIENT_SECRET=your-google-client-secret
GOOGLE_OAUTH_USER_MODEL=App\Models\User  # optional, defaults to App\Models\User
GOOGLE_OAUTH_GOOGLE_ID_COLUMN=google_id  # optional, for custom column name
GOOGLE_OAUTH_EMAIL_COLUMN=email          # optional, for custom column name
GOOGLE_OAUTH_DOMAIN_WHITELIST=your-company.com,partner.com  # optional, comma-separated
```

### Step 4: Ensure Users Table Has Required Columns

[](#step-4-ensure-users-table-has-required-columns)

If you need to store Google user IDs, add a migration:

```
Schema::table('users', function (Blueprint $table) {
    $table->string('google_id')->nullable()->unique();
});
```

Run migrations:

```
php artisan migrate
```

Configuration
-------------

[](#configuration)

The package exposes configuration in `config/google-oauth.php`:

```
return [
    // Google OAuth credentials (from Google Cloud Console)
    'google_client_id' => env('GOOGLE_OAUTH_CLIENT_ID'),
    'google_client_secret' => env('GOOGLE_OAUTH_CLIENT_SECRET'),

    // Which user model to query for authentication
    'user_model' => env('GOOGLE_OAUTH_USER_MODEL', 'App\Models\User'),

    // Column mapping for flexible schema
    'column_mapping' => [
        'email' => env('GOOGLE_OAUTH_EMAIL_COLUMN', 'email'),
        'google_id' => env('GOOGLE_OAUTH_GOOGLE_ID_COLUMN', 'google_id'),
    ],

    // Whitelist by domain (null = allow all domains)
    'domain_whitelist' => env('GOOGLE_OAUTH_DOMAIN_WHITELIST')
        ? explode(',', env('GOOGLE_OAUTH_DOMAIN_WHITELIST'))
        : null,

    // Passport token expiry (seconds)
    'token_expires_in' => 31536000, // 1 year
];
```

### Column Mapping

[](#column-mapping)

If your users table uses different column names:

```
# For a table with: google_user_id, employee_email
GOOGLE_OAUTH_GOOGLE_ID_COLUMN=google_user_id
GOOGLE_OAUTH_EMAIL_COLUMN=employee_email
```

### Domain Whitelist

[](#domain-whitelist)

Restrict authentication to specific email domains:

```
GOOGLE_OAUTH_DOMAIN_WHITELIST=company.com,partner.com
```

Only users with email addresses ending in these domains can authenticate. Leave empty/null to allow all domains.

Usage
-----

[](#usage)

### Authentication Flow

[](#authentication-flow)

1. **External app** (e.g., chatbot) obtains Google OAuth token from user
2. **External app** sends token to your Laravel endpoint: `POST /oauth/google-token`
3. **Your package** validates token with Google, finds user by email
4. **Your package** issues Passport personal access token
5. **External app** uses Passport token for subsequent API calls

### HTTP Endpoint

[](#http-endpoint)

**POST** `/oauth/google-token`

#### Request

[](#request)

```
curl -X POST http://localhost:8000/oauth/google-token \
  -H "Content-Type: application/json" \
  -d '{
    "access_token": "ya29.a0AfH6SMBx...",
    "refresh_token": "1//0gx..."
  }'
```

**Parameters:**

- `access_token` (required): Google OAuth access token
- `refresh_token` (optional): Google OAuth refresh token (used if access token is expired)

#### Success Response (200 OK)

[](#success-response-200-ok)

```
{
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
  "token_type": "Bearer",
  "expires_in": 31536000
}
```

Use the `access_token` as a Bearer token in subsequent API requests:

```
curl -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9..." \
  http://localhost:8000/api/protected-endpoint
```

#### Error Responses

[](#error-responses)

**400 Bad Request** — Validation failed

```
{
  "error": "invalid_request",
  "message": {
    "access_token": ["The access_token field is required."]
  }
}
```

**401 Unauthorized** — Token invalid, expired, or user not found

```
{
  "error": "invalid_grant",
  "message": "The provided token is invalid or tampered with."
}
```

Other 401 errors:

- `token_expired`: Token is expired (no refresh token provided)
- `user_not_found`: User email not in database
- `domain_not_allowed`: User email domain not whitelisted

**503 Service Unavailable** — Google API error

```
{
  "error": "service_unavailable",
  "message": "Google API returned status 503"
}
```

User Provisioning
-----------------

[](#user-provisioning)

Users **must exist** in your database before authenticating. The package does not auto-create users to prevent account enumeration attacks.

### Provisioning Workflow

[](#provisioning-workflow)

1. **Add user to database** (via admin panel, bulk import, or migration):

```
User::create([
    'name' => 'John Doe',
    'email' => 'john@company.com',
    'google_id' => 'google-user-id-123',  // optional, from Google tokeninfo
    'password' => Hash::make(Str::random(32)), // optional, can be dummy
]);
```

2. **User authenticates** via `POST /oauth/google-token`
3. **Package validates** email matches (finds user)
4. **Package issues** Passport token

### Pre-provisioning at Scale

[](#pre-provisioning-at-scale)

For bulk onboarding:

```
# CSV import
php artisan import:users users.csv --from-google

# Sync from directory (LDAP, Entra, etc.)
php artisan sync:users --provider=entra
```

Security Considerations
-----------------------

[](#security-considerations)

### Production Checklist

[](#production-checklist)

- ✅ Use HTTPS only (enforce via middleware or load balancer)
- ✅ Store credentials in `.env` (never commit to version control)
- ✅ Pre-provision users carefully (audit logging recommended)
- ✅ Monitor rate limiting (`/oauth/google-token` max 60 requests/minute per IP)
- ✅ Use domain whitelist in production
- ✅ Rotate Google OAuth credentials regularly

### How It Works

[](#how-it-works)

1. **No token storage** — Tokens validated in-memory, never persisted
2. **Server-side refresh** — If token expired but refresh token provided, package refreshes silently
3. **Email-based lookup** — User identity determined by email (not Google ID)
4. **Pre-provisioning only** — Prevents account enumeration
5. **Signed Passport tokens** — Subsequent API calls verified with Passport

Configuration Per Application
-----------------------------

[](#configuration-per-application)

Each Laravel app using this package can customize the configuration independently:

**MCP Server** (`config/google-oauth.php`):

```
'column_mapping' => ['email' => 'email', 'google_id' => 'google_user_id'],
'domain_whitelist' => ['company.com'],
```

**Internal App** (`config/google-oauth.php`):

```
'column_mapping' => ['email' => 'work_email', 'google_id' => 'gid'],
'domain_whitelist' => ['company.com', 'internal.company.com'],
```

**Partner Portal** (`config/google-oauth.php`):

```
'column_mapping' => ['email' => 'contact_email', 'google_id' => null],
'domain_whitelist' => ['partner1.com', 'partner2.com'],
```

Testing
-------

[](#testing)

### Running Tests

[](#running-tests)

```
# All tests
vendor/bin/phpunit tests/

# Unit tests only
vendor/bin/phpunit tests/Unit/

# Feature tests only
vendor/bin/phpunit tests/Feature/

# Specific test
vendor/bin/phpunit tests/Unit/GoogleTokenValidatorTest.php
```

### Test Coverage

[](#test-coverage)

- **18 tests passing** (9 skipped for Passport integration)
- Unit tests for token validation and user resolution
- Feature tests for controller behavior
- Integration tests for end-to-end flow
- HTTP mocking for Google API (no external dependencies)

### Writing Integration Tests

[](#writing-integration-tests)

In your app, add integration tests:

```
use Illuminate\Support\Facades\Http;

public function test_google_oauth_integration()
{
    Http::fake([
        'https://www.googleapis.com/oauth2/v3/tokeninfo' => Http::response([
            'email' => 'test@company.com',
            'user_id' => 'google-123',
            'expires_in' => 3600,
        ]),
    ]);

    // Create user
    $user = User::factory()->create(['email' => 'test@company.com']);

    // Exchange token
    $response = $this->postJson('/oauth/google-token', [
        'access_token' => 'test-token',
    ]);

    $response->assertStatus(200)
        ->assertJsonStructure(['access_token', 'token_type', 'expires_in']);
}
```

Quality Checks
--------------

[](#quality-checks)

The package includes code quality tools:

```
# Code style (PSR-12)
vendor/bin/pint src tests

# Static analysis (PHPStan level 5)
vendor/bin/phpstan analyse src
```

All quality gates pass:

- ✅ Pint (0 style issues)
- ✅ PHPStan (0 errors)
- ✅ PHPUnit (18 passing tests)

Troubleshooting
---------------

[](#troubleshooting)

### "User not found" (401)

[](#user-not-found-401)

- Verify user exists: `User::where('email', 'test@company.com')->first()`
- Check column mapping: `GOOGLE_OAUTH_EMAIL_COLUMN`
- Verify email matches exactly (case-sensitive)

### "Domain not allowed" (401)

[](#domain-not-allowed-401)

- Check whitelist: `config('google-oauth.domain_whitelist')`
- Verify user's email domain is whitelisted
- To disable: `GOOGLE_OAUTH_DOMAIN_WHITELIST=` (empty value)

### "Token expired" (401)

[](#token-expired-401)

- Provide `refresh_token` in request body
- Ensure Google OAuth credentials are correct
- Verify token is actually expired (check `expires_in`)

### "Service unavailable" (503)

[](#service-unavailable-503)

- Check Google API status:
- Verify `GOOGLE_OAUTH_CLIENT_ID` and `GOOGLE_OAUTH_CLIENT_SECRET`
- Check network connectivity to `https://www.googleapis.com`

### Tests Failing

[](#tests-failing)

- Ensure Composer dev dependencies installed: `composer install`
- Run with clean cache: `vendor/bin/phpunit --no-coverage`
- Check for mocking issues: Review `Http::fake()` setup in test

API Reference
-------------

[](#api-reference)

### GoogleTokenValidator

[](#googletokenvalidator)

Validates Google OAuth tokens and handles refresh logic.

```
use Impression\GooglePassportOAuth\Services\GoogleTokenValidator;

$validator = app(GoogleTokenValidator::class);

// Validate token (with optional refresh)
$tokenInfo = $validator->validate('access-token', 'refresh-token');

// Returns:
// [
//     'email' => 'user@example.com',
//     'name' => 'user@example.com',
//     'google_id' => 'google-123',
//     'expires_at' => Carbon instance,
// ]
```

### UserSyncResolver

[](#usersyncresolver)

Finds users by email with domain whitelist enforcement.

```
use Impression\GooglePassportOAuth\Services\UserSyncResolver;

$resolver = app(UserSyncResolver::class);

// Resolve user (throws if not found or domain not allowed)
$user = $resolver->resolveByEmail('user@example.com');
```

### Exceptions

[](#exceptions)

All exceptions extend `GoogleOAuthException`:

```
use Impression\GooglePassportOAuth\Exceptions\{
    InvalidTokenException,
    TokenExpiredException,
    RefreshFailedException,
    GoogleApiException,
    UserNotFoundByEmailException,
    DomainNotAllowedException,
};
```

License
-------

[](#license)

MIT

Contributing
------------

[](#contributing)

Contributions welcome! Please ensure:

- Tests pass: `vendor/bin/phpunit`
- Code style passes: `vendor/bin/pint src tests`
- Static analysis passes: `vendor/bin/phpstan analyse src`

Support
-------

[](#support)

For issues and questions, please open an issue on GitHub or contact the Impression team.

###  Health Score

36

—

LowBetter than 79% of packages

Maintenance91

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

42d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/84cc83d024bf1d44f6bae42d7a5fe788b08df3d46448df18ed001f62fb470480?d=identicon)[Impression](/maintainers/Impression)

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/impression-laravel-google-passport-oauth/health.svg)

```
[![Health](https://phpackages.com/badges/impression-laravel-google-passport-oauth/health.svg)](https://phpackages.com/packages/impression-laravel-google-passport-oauth)
```

###  Alternatives

[unopim/unopim

UnoPim Laravel PIM

10.1k2.2k](/packages/unopim-unopim)[nasirkhan/laravel-starter

A CMS like modular Laravel starter project.

1.4k2.7k](/packages/nasirkhan-laravel-starter)[jeremy379/laravel-openid-connect

OpenID Connect support to the PHP League's OAuth2 Server. Compatible with Laravel Passport.

58403.6k8](/packages/jeremy379-laravel-openid-connect)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
