forgelab-me / ci4-trusted-publishing
Let a CI workflow authenticate to your CodeIgniter 4 app with the identity token its runner mints, and get a short-lived Shield access token in exchange — no long-lived secret stored in the repository.
Package info
github.com/forgelab-me/ci4-trusted-publishing
pkg:composer/forgelab-me/ci4-trusted-publishing
Requires
- php: ^8.2
- ext-openssl: *
- firebase/php-jwt: ^6.10 || ^7.0
Requires (Dev)
- codeigniter4/framework: ^4.7
- codeigniter4/shield: ^1.4
- phpunit/phpunit: ^11.5
Suggests
- codeigniter4/shield: Required by ScopedTokens — minting, listing and purging the access tokens this package hands out.
README
Let a CI workflow authenticate to your CodeIgniter 4 application with the identity token its runner mints for it, and hand it a short-lived Shield access token in exchange.
No long-lived secret in the repository settings. Nothing to rotate. A stolen pipeline log yields a credential that expired fifteen minutes ago.
POST /api/my-app/publish/token Authorization: Bearer <GitHub OIDC token>
→ { "token": "…", "expires_in": 900, "scope": "publish:my-app" }
What it does, and what it leaves to you
It owns identity: verifying somebody else's signed statement about which repository, workflow and environment is running, and deciding whether that matches something you already trust.
You own authorization: what that identity may then do, and where the trust
is recorded. That boundary is deliberate — one application grants
publish:{slug} on a project, another grants a package-push scope on a feed
with its own pattern rules. Neither belongs in a shared package.
| In here | verifying the token (signature, issuer, audience), normalising claims across providers, matching a verified identity against your trusted publishers, minting/listing/revoking/purging scoped Shield tokens |
| Yours | the trusted publishers table and its foreign key, the route and controller, what a scope permits, the admin screen |
Requirements
- PHP 8.2+
- CodeIgniter 4.7+
- codeigniter4/shield 1.4+ for the
token side (
ScopedTokens); the verification side works without it
Install
composer require forgelab-me/ci4-trusted-publishing
The shape of it
use Forgelabme\TrustedPublishing\Config\TrustedPublishing; use Forgelabme\TrustedPublishing\PublisherMatcher; use Forgelabme\TrustedPublishing\ScopedTokens; use Forgelabme\TrustedPublishing\Verifier; // 1. Verify what the runner presented. $result = config(TrustedPublishing::class) ->verifier('github') ->verify(Verifier::bearer($this->request->getHeaderLine('Authorization'))); if (! $result->ok) { return $this->failUnauthorized($result->error); } // 2. Is this identity one you already trust? Candidates come from your table. $publisher = (new PublisherMatcher())->match( $publishers->forProject($project['id']), $result->identity, ); if ($publisher === null) { return $this->failForbidden('No trusted publisher matches this token.'); } // 3. Hand back a credential that expires on its own. ScopedTokens::purgeExpired($scope); $token = ScopedTokens::mint($user, $result->identity->describe(), [$scope], 15); return $this->respond(['token' => $token->raw_token, 'expires_in' => 900, 'scope' => $scope]);
Documentation
- Integrating it — the table, the endpoint, the workflow, the admin screen, and how to test your own integration. Start here.
- Providers — how GitHub Actions is wired, and how to add GitLab CI or anything else that issues OIDC tokens.
- Security — what each check is for, and the mistakes this package exists to stop you making twice.
Licence
MIT — see LICENSE.