This package provides Geocaching OAuth 2.0 support for the PHP League's OAuth 2.0 Client.
To install, use composer:
composer require surfoo/oauth2-geocaching
Usage is the same as The League's OAuth client, using \League\OAuth2\Client\Provider\Geocaching as the provider.
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() . '!';
}| 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 |
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));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'
);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.
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 settingBy 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 APIThis 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 on401and 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.
$ ./vendor/bin/phpunitThe MIT License (MIT). Please see License File for more information.