surfoo/oauth2-geocaching

Geocaching OAuth 2.0 Client Provider for The PHP League OAuth2-Client

Maintainers

Package info

github.com/Surfoo/oauth2-geocaching

pkg:composer/surfoo/oauth2-geocaching

Statistics

Installs: 4 833

Dependents: 1

Suggesters: 0

Stars: 3

Open Issues: 0

3.0.0 2026-06-28 21:44 UTC

This package is auto-updated.

Last update: 2026-06-28 21:47:40 UTC


README

Software License Total Downloads

This package provides Geocaching OAuth 2.0 support for the PHP League's OAuth 2.0 Client.

Installation

To install, use composer:

composer require surfoo/oauth2-geocaching

Usage

Usage is the same as The League's OAuth client, using \League\OAuth2\Client\Provider\Geocaching as the provider.

Authorization Code Flow

use League\OAuth2\Client\Provider\Geocaching;

$provider = new Geocaching([
    'clientId'     => 'your-client-id',
    'clientSecret' => 'your-client-secret',
    'redirectUri'  => 'https://your-app.com/callback',
    'environment'  => 'production', // 'dev', 'staging', 'production'
]);

// Get authorization URL
$authUrl = $provider->getAuthorizationUrl(['scope' => '*']);
$_SESSION['oauth2state'] = $provider->getState();

// Redirect user to Geocaching
header('Location: ' . $authUrl);
exit;

// In your callback handler
if (!empty($_GET['code'])) {
    $token = $provider->getAccessToken('authorization_code', [
        'code' => $_GET['code']
    ]);

    $user = $provider->getResourceOwner($token);
    echo 'Hello ' . $user->getUsername() . '!';
}

Environments

Environment Description URLs
production, prod Official Geocaching.com geocaching.com, api.groundspeak.com
staging, qa Staging environment staging.geocaching.com
dev, development, docker, test Local development localhost:8000

Custom URLs for Development

You can override environment URLs for your own development infrastructure:

use League\OAuth2\Client\Provider\Geocaching;
use League\OAuth2\Client\Provider\GeocachingConfig;

// Method 1: Direct URL overrides
$provider = new Geocaching([
    'clientId' => 'dev-client-id',
    'clientSecret' => 'dev-secret',
    'environment' => 'dev',
    'redirectUri' => 'http://localhost:3000/callback',

    // Custom URLs (override environment defaults)
    'domain' => 'https://my-geocaching.local',
    'apiDomain' => 'https://api.my-geocaching.local',
    'oAuthDomain' => 'https://oauth.my-geocaching.local',
]);

// Method 2: Using configuration helper
$config = GeocachingConfig::create('staging', [
    'apiDomain' => 'https://my-internal-api.company.com'
]);

$provider = new Geocaching(array_merge([
    'clientId' => 'client-id',
    'clientSecret' => 'client-secret',
    'redirectUri' => 'https://app.company.com/callback',
], $config));

Factory Helpers for Common Patterns

use League\OAuth2\Client\Test\Factory\GeocachingTestFactory;

// Docker with custom port
$provider = GeocachingTestFactory::createForDocker(9000);
// Results in: http://localhost:9000, http://localhost:9000/api, http://localhost:9000/oauth

// Local development with custom base URL
$provider = GeocachingTestFactory::createForLocalDev('http://192.168.1.100:8080');
// Results in: http://192.168.1.100:8080, http://192.168.1.100:8080/api/v1, http://192.168.1.100:8080/oauth

// Completely custom URLs
$provider = GeocachingTestFactory::createWithCustomUrls(
    'https://geocaching.mycompany.local',
    'https://api.geocaching.mycompany.local',
    'https://oauth.geocaching.mycompany.local'
);

Advanced Configuration Examples

Docker Compose Setup:

$provider = new Geocaching([
    'clientId' => $_ENV['GEOCACHING_CLIENT_ID'],
    'clientSecret' => $_ENV['GEOCACHING_CLIENT_SECRET'],
    'environment' => 'dev',
    'redirectUri' => 'http://localhost:3000/auth/callback',

    'domain' => 'http://geocaching-web:80',
    'apiDomain' => 'http://geocaching-api:8080/v1',
    'oAuthDomain' => 'http://geocaching-oauth:9090',
]);

Enterprise Infrastructure:

$config = GeocachingConfig::create('production', [
    'domain' => 'https://geocaching.internal.company.com',
    'apiDomain' => 'https://geocaching-api.internal.company.com/v2',
    'oAuthDomain' => 'https://sso.company.com/geocaching',
]);

$provider = new Geocaching(array_merge([
    'clientId' => $_ENV['COMPANY_GEOCACHING_CLIENT_ID'],
    'clientSecret' => $_ENV['COMPANY_GEOCACHING_CLIENT_SECRET'],
    'redirectUri' => 'https://myapp.company.com/oauth/callback',
], $config));

Multi-Environment Deployment:

$environment = $_ENV['APP_ENV'] ?? 'production';

switch ($environment) {
    case 'local':
        $provider = GeocachingTestFactory::createForLocalDev($_ENV['DEV_BASE_URL']);
        break;
    case 'staging':
        $config = GeocachingConfig::create('staging', [
            'apiDomain' => $_ENV['STAGING_API_URL'],
        ]);
        $provider = new Geocaching(array_merge($baseOptions, $config));
        break;
    case 'production':
    default:
        $provider = new Geocaching(array_merge($baseOptions, [
            'environment' => 'production'
        ]));
        break;
}

Take a look at demo/index.php for complete examples.

Resource Owner (User Data)

After obtaining an access token, you can retrieve user information:

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

// Basic information
echo $user->getReferenceCode();    // 'PR1ABC2'
echo $user->getUsername();         // 'MyUsername'
echo $user->getFindCount();        // 150
echo $user->getHideCount();        // 5
echo $user->getFavoritePoints();   // 25

// Profile information
echo $user->getMembershipLevelId(); // 3 (Premium membership)
echo $user->getJoinedDate();       // '2020-01-15T10:30:00.123'
echo $user->getAvatarUrl();        // URL to user's avatar image
echo $user->getProfileUrl();       // URL to user's public profile
echo $user->getProfileText();      // User's profile description

// Additional data
$coordinates = $user->getHomeCoordinates(); // Array with lat/lon
$geocacheLimits = $user->getGeocacheLimits(); // API usage limits
$friendSharing = $user->getOptedInFriendSharing(); // Privacy setting

Custom Resource Owner Fields

By default, the provider requests a standard set of user fields. You can customize which fields to retrieve:

$provider->setResourceOwnerFields([
    'referenceCode',
    'username',
    'findCount',
    'hideCount',
    'favoritePoints',
    'membershipLevelId',
    'joinedDateUtc',
    'avatarUrl',
    'profileText'
    // Add any other fields supported by the Geocaching API
]);

$user = $provider->getResourceOwner($token);
// Now only the specified fields will be requested from the API

Token Management & Refresh

This package ships token lifecycle utilities you can plug into any PSR-18 client:

  • TokenSet: lightweight DTO for access/refresh tokens with expiry helpers.
  • TokenStorageInterface: implement to persist tokens (DB, cache, file) with locking.
  • TokenRefreshPlugin: HTTPlug/PSR plugin that refreshes tokens on 401 and retries the original request.
  • Exceptions for refresh/storage errors: TokenRefreshException, RefreshTokenExpiredException, TokenStorageException.

Basic wiring:

use Http\Client\Common\PluginClientFactory;
use League\OAuth2\Client\Plugin\TokenRefreshPlugin;
use League\OAuth2\Client\Provider\Geocaching;
use League\OAuth2\Client\Token\TokenStorageInterface;
use League\OAuth2\Client\Token\TokenSet;
use Nyholm\Psr7\Request;
use Psr\Log\NullLogger;

$provider = new Geocaching([
    'clientId'     => 'client_id',
    'clientSecret' => 'client_secret',
    'redirectUri'  => 'https://your-app.test/callback',
    'environment'  => 'production', // or staging/dev
]);

$storage = new class implements TokenStorageInterface {
    private ?TokenSet $tokens = null;
    public function getTokens(string $referenceCode): ?TokenSet { return $this->tokens; }
    public function saveTokens(string $referenceCode, TokenSet $tokens): void { $this->tokens = $tokens; }
    public function lockUser(string $referenceCode, int $timeoutSeconds = 30): bool { return true; }
    public function unlockUser(string $referenceCode): void {}
    public function isUserLocked(string $referenceCode): bool { return false; }
};

$refreshPlugin = new TokenRefreshPlugin(
    referenceCode: 'PR12345',
    storage: $storage,
    oauthProvider: $provider,
    logger: new NullLogger(),
    maxRetryAttempts: 3
);

$httpClient = (new PluginClientFactory())->createClient(
    \Http\Discovery\Psr18ClientDiscovery::find(),
    [$refreshPlugin]
);

$request = new Request('GET', $provider->apiDomain . '/v1/users/PR12345');
$response = $httpClient->sendRequest($request);

See demo/index.php for a full flow including PKCE, token storage, and a sample API call with automatic refresh. In production, replace the in-memory storage with a durable implementation.

Testing

$ ./vendor/bin/phpunit

License

The MIT License (MIT). Please see License File for more information.