greghanton/simplifi-hq-oauth-api
Composer 安装命令:
composer require greghanton/simplifi-hq-oauth-api
包简介
OAuth2 client and request dispatcher for the Joy Pilot API tier
README 文档
README
OAuth2 client and request dispatcher for the Joy Pilot API tier.
Install
composer require greghanton/simplifi-hq-oauth-api
Configuration
This package loads defaults from config.php and then reads values from environment variables using simplifiHqOauthApiLibEnv().
Environment variables
| Variable | Required | Default | Notes |
|---|---|---|---|
SIMPLIFI_API_GRANT_TYPE |
No | client_credentials |
The only grant this package itself can mint. The password grant is disabled server-side (not just deprecated) — see Per-consumer grant matrix. |
SIMPLIFI_API_CLIENT_ID |
Yes | - | OAuth client id. |
SIMPLIFI_API_CLIENT_SECRET |
Yes | - | OAuth client secret. |
SIMPLIFI_API_USERNAME |
No (legacy) | - | Dead unless a future grant reintroduces it; the password grant that read this is disabled server-side. |
SIMPLIFI_API_PASSWORD |
No (legacy) | - | Dead alongside SIMPLIFI_API_USERNAME, same reason. |
SIMPLIFI_API_SCOPE |
No | * |
OAuth scope, forwarded as-is to /oauth/token. |
SIMPLIFI_API_ALLOW_SERVICE_MINT |
No | true |
Set false on any per-session/user-context config (see User-scoped tokens). Leave true (default) on the global/anonymous config. |
SIMPLIFI_API_URL_BASE |
Yes | - | API base URL, e.g. https://api.example.com/. |
SIMPLIFI_API_ACCESS_TOKEN_STORE_AS |
No | temp_file |
custom is recommended for production, temp_file is fine for local/dev. |
SIMPLIFI_API_ACCESS_TOKEN_TEMP_FILE_FILENAME |
No | Derived from project path/version | Only used when store_as=temp_file. |
SIMPLIFI_API_ACCESS_TOKEN_CUSTOM_KEY |
For custom store |
simplifi-hq-oauth-api-access-token |
Key passed to custom callables. |
SIMPLIFI_API_ACCESS_TOKEN_GET |
For custom store |
- | JSON-encoded callable. Signature: get($customKey): ?string. |
SIMPLIFI_API_ACCESS_TOKEN_SET |
For custom store |
- | JSON-encoded callable. Signature: set($customKey, $tokenJson): mixed. |
SIMPLIFI_API_ACCESS_TOKEN_DEL |
For custom store |
- | JSON-encoded callable. Signature: del($customKey): mixed. |
SIMPLIFI_API_ACCESS_TOKEN_LOCK |
Optional | - | JSON-encoded callable. Signature: lock($customKey, $ttlSeconds): bool. |
SIMPLIFI_API_ACCESS_TOKEN_UNLOCK |
Optional | - | JSON-encoded callable. Signature: unlock($customKey): mixed. |
SIMPLIFI_API_ERROR_LOG_FUNCTION |
No | "error_log" |
JSON-encoded callable for internal error logging. |
SIMPLIFI_API_DEFAULT_HEADERS |
No | [] |
JSON object of headers merged into each request. |
SIMPLIFI_API_SSL_VERIFY |
No | true |
TLS certificate verification for HTTP requests. |
SIMPLIFI_API_ADD_TRACE_DEBUG_HEADER |
No | false |
Adds trace-debug-header from caller backtrace. |
Basic usage
Synchronous request
<?php require __DIR__.'/vendor/autoload.php'; use SimplifiApi\ApiRequest; $response = ApiRequest::request([ 'method' => 'GET', 'url' => 'sales', ]); if (! $response->success()) { throw new RuntimeException($response->errorsToString()); } foreach ($response as $row) { // ApiResponse implements Iterator when response has a top-level "data" array. var_dump($row); }
Async request
<?php use SimplifiApi\ApiRequest; $promise = ApiRequest::requestAsync([ 'method' => 'GET', 'url' => 'sales', ]); $response = $promise->wait(); if ($response->success()) { var_dump($response->response()); }
Batch request
<?php use SimplifiApi\ApiRequest; $responses = ApiRequest::batch([ ['method' => 'GET', 'url' => 'sales'], ['method' => 'GET', 'url' => ['sales/$/invoice', 102]], ]); foreach ($responses as $response) { if (! $response->success()) { error_log($response->errorsToString()); } }
Access token storage
Supported modes:
custom(recommended for production; usually Redis)temp_file(unchanged local/dev fallback)
Redis setup via custom callables
SIMPLIFI_API_ACCESS_TOKEN_STORE_AS=custom SIMPLIFI_API_ACCESS_TOKEN_CUSTOM_KEY=simplifi-hq-oauth-api-access-token SIMPLIFI_API_ACCESS_TOKEN_GET="[\"\\\\App\\\\Cache\\\\TokenStore\", \"get\"]" SIMPLIFI_API_ACCESS_TOKEN_SET="[\"\\\\App\\\\Cache\\\\TokenStore\", \"set\"]" SIMPLIFI_API_ACCESS_TOKEN_DEL="[\"\\\\App\\\\Cache\\\\TokenStore\", \"del\"]"
Laravel example:
<?php namespace App\Cache; use Illuminate\Support\Facades\Cache; class TokenStore { public static function get(string $key): ?string { return Cache::store('redis')->get($key); } public static function set(string $key, string $value): bool { return Cache::store('redis')->forever($key, $value); } public static function del(string $key): bool { return Cache::store('redis')->forget($key); } }
Token refresh mutex (optional, recommended in production)
When several processes see an expired token at the same time, configure lock/unlock callables to prevent duplicate oauth/token refresh requests.
SIMPLIFI_API_ACCESS_TOKEN_LOCK="[\"\\\\App\\\\Cache\\\\TokenLock\", \"acquire\"]" SIMPLIFI_API_ACCESS_TOKEN_UNLOCK="[\"\\\\App\\\\Cache\\\\TokenLock\", \"release\"]"
Laravel lock example:
<?php namespace App\Cache; use Illuminate\Support\Facades\Cache; class TokenLock { private static array $locks = []; public static function acquire(string $customKey, int $ttl): bool { $lockName = $customKey.':oauth-refresh-lock'; $lock = Cache::store('redis')->lock($lockName, $ttl); if (! $lock->get()) { return false; } self::$locks[$lockName] = $lock; return true; } public static function release(string $customKey): void { $lockName = $customKey.':oauth-refresh-lock'; if (! isset(self::$locks[$lockName])) { return; } self::$locks[$lockName]->release(); unset(self::$locks[$lockName]); } }
Mutex flow inside the package:
- Try to lock (currently with ~10s TTL).
- If lock is acquired, refresh token and unlock.
- If lock is not acquired, wait 1-2s, re-check cache, and reuse token if present.
- Only fetch a fresh token if cache is still empty after waiting.
Event listener examples
Use listeners for metrics, tracing, or debugging without changing package internals.
<?php use SimplifiApi\ApiRequest; use SimplifiApi\ApiResponse; ApiRequest::addEventListener(ApiRequest::EVENT_BEFORE_REQUEST, function (array $requestOptions, array $config): void { // Example: emit request-start metric. }); ApiRequest::addEventListener(ApiRequest::EVENT_AFTER_REGULAR_REQUEST, function (ApiResponse $response): void { // Example: emit sync request timing metric. }); ApiRequest::addEventListener(ApiRequest::EVENT_AFTER_ASYNC_REQUEST, function (ApiResponse $response): void { // Example: emit async request metric. }); ApiRequest::addEventListener(ApiRequest::EVENT_AFTER_BATCH_REQUEST, function (ApiResponse $response): void { // Example: emit per-item batch metric. }); ApiResponse::addEventListener(ApiResponse::EVENT_RESPONSE_CREATED, function (ApiResponse $response): void { // Example: central hook after any response object is created. });
Laravel config:cache gotcha
src/helpers.php resolves env values in this order:
simplifiHqOauthApiEnv($key, $default)if your app defines it- fallback to Laravel
env($key, $default)if available - fallback default
With Laravel config:cache, runtime env() calls outside config files return null. To keep custom token store and mutex callables reliable under cached config, define simplifiHqOauthApiEnv() in your app and read from your own config layer instead of direct runtime env().
Per-consumer grant matrix
The API tier's identity model (Stage 1.5) draws a hard line between three kinds of token, and this package only ever mints one of them. Use this table to decide how each consumer should be configured:
| Consumer | Grant this package mints | How the token is bound | Notes |
|---|---|---|---|
| Anonymous server-to-server (password reset, signup, email verification, the public login/onboarding front doors) | client_credentials via SIMPLIFI_API_GRANT_TYPE=client_credentials |
No actor — app-level only | This is the only flow this package's AccessToken::generateNewAccessToken() performs. Use a narrowed service scope (e.g. svc:auth/svc:onboarding), not *, where the client supports it. Cached under the global custom_key. |
| First-party user-scoped (a logged-in GUI/No Worries/Apex Wealth session) | Not minted by this package. The consumer calls POST /oauth/token directly with the API's login_token grant (exchanging the one-time login token issued at login) to get the user's access token, and grant_type=refresh_token to renew it. |
Per-session — the consumer writes the token into a per-session custom_key (see User-scoped tokens) |
password is disabled server-side — do not configure it for this case. This package only ever reads the token the consumer already obtained. |
| Third-party / MCP / AI integrations | Not via this package at all | authorization_code + PKCE, entity-bound |
These consumers talk to Passport's authorization-code flow directly; listed here for completeness only. |
User-scoped tokens: the package never mints them
Per the Stage 1.5 identity model, the GUI/consumer owns acquiring and refreshing user-scoped tokens; this package only owns using them. Concretely:
- At login, the consumer exchanges its one-time login token for a user-scoped access + refresh
token pair directly against
/oauth/token(grant_type=login_token), outside this package. - The consumer writes that token into a per-session
customstore — i.e. acustom_keythat is unique per user session, not the shared service-token key — using the sameget/set/delcallable hooks described above (Redis setup). - The consumer calls
ApiRequest::request($options, $overrideConfig)with$overrideConfigpointingaccess_token.custom.custom_key(andget/set/del) at that per-session store. $overrideConfigmust also set'allow_service_credential_mint' => false.
That last step matters: without it, if the per-session cache entry is missing or expired (token
revoked, refresh not yet run, cache evicted), AccessToken::generateNewAccessToken() would fall
through to minting a service (client_credentials) token from the global
client_id/client_secret — and cache it under that user's session key. The next request would
then silently run as the service account while believing it was acting as that user — exactly
the identity-laundering hole the Stage 1.5 model closes. With allow_service_credential_mint=false
set, a cache miss on a user-context config fails closed instead: ApiRequest::request() returns a
non-success ApiResponse (no HTTP call is made), and the consumer should re-authenticate or run its
own refresh flow rather than receive a token at all.
<?php use SimplifiApi\ApiRequest; // Per-session config for an authenticated user — set once after login/refresh writes the // token into Redis under $sessionCustomKey. $response = ApiRequest::request( ['method' => 'GET', 'url' => 'sales'], [ 'access_token' => [ 'store_as' => 'custom', 'custom' => [ 'custom_key' => $sessionCustomKey, 'get' => ['App\\Cache\\TokenStore', 'get'], 'set' => ['App\\Cache\\TokenStore', 'set'], 'del' => ['App\\Cache\\TokenStore', 'del'], ], ], 'allow_service_credential_mint' => false, ] );
Leave allow_service_credential_mint unset (defaults true) on the global/anonymous config used
for client_credentials calls — there is no user identity to protect there, and that path is
exactly what generateNewAccessToken() is for.
Development
composer run lint:check
composer run stan
composer run test
Docs
greghanton/simplifi-hq-oauth-api 适用场景与选型建议
greghanton/simplifi-hq-oauth-api 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 1.17k 次下载、GitHub Stars 达 0, 最近一次更新时间为 2020 年 05 月 25 日, 在 PHP 生态内属于活跃度较高的组件。
我们在过去多个企业项目中使用过 greghanton/simplifi-hq-oauth-api 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 greghanton/simplifi-hq-oauth-api 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
统计信息
- 总下载量: 1.17k
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 20
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: proprietary
- 更新时间: 2020-05-25