Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Edgegrid Auth Laravel Package

akamai-open/edgegrid-auth

PHP implementation of Akamai EdgeGrid authentication. It signs requests to Akamai's REST APIs by generating the required Authorization header with timestamp, nonce, and content hash, so you can securely call Fast Purge, Property Manager, and other Akamai OPEN APIs from your PHP applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require akamai-open/edgegrid-auth

The package is signing-only: it produces an Authorization: EG1-HMAC-SHA256 ... header and nothing else. It ships no HTTP client, no retry logic, and no endpoint models — you bring Guzzle, Laravel's Http facade, or plain cURL.

Get credentials first. In Akamai Control Center go to Identity & Access Management → API clients, create a client, and copy the four values (they are shown once):

  • host — e.g. akab-xxxxxxxxxxxx-xxxxxxxxxxxx.luna.akamaiapis.net (no scheme, no trailing slash)
  • client_token
  • access_token
  • client_secret

Give the client the API service grants for whatever you're calling (PAPI, Fast Purge, Edge Diagnostics, etc.) — signing will succeed and you'll still get 403 on the API layer if the grant is missing.

First use case — sign one request and fire it

use Akamai\Open\EdgeGrid\Auth\Authentication;

$auth = (new Authentication())
    ->setHost('akab-xxxxxxxxxxxx-xxxxxxxxxxxx.luna.akamaiapis.net')
    ->setClientToken(config('services.akamai.client_token'))
    ->setAccessToken(config('services.akamai.access_token'))
    ->setClientSecret(config('services.akamai.client_secret'));

$path  = '/papi/v1/contracts';
$query = '';

$auth->setHttpMethod('GET')
     ->setPath($path)
     ->setQuery($query)
     ->setHeaders(['Host' => $auth->getHost()]);

$header = $auth->createAuthHeader(); // "EG1-HMAC-SHA256 client_token=...;access_token=...;..."

$response = Http::withHeaders(['Authorization' => $header])
    ->get('https://' . $auth->getHost() . $path . ($query ? '?' . $query : ''));

Where to look first

  1. src/Auth/Authentication.php in the monorepo — it is ~400 lines and is the entire package. Read createAuthHeader(), makeBaseString(), and canonicalizeHeaders() before debugging anything.
  2. The monorepo README section for the auth split (the issues tracker lives on the monorepo, not the split repo).
  3. The EdgeGrid spec on Akamai's developer docs for the canonical-request rules — the library implements them literally, so when something 403s the spec is the tiebreaker.

Implementation Patterns

Pattern 1: A bound signer service (the default in Laravel)

Never scatter new Authentication() through controllers. Bind it once:

// config/services.php
'akamai' => [
    'host'          => env('AKAMAI_HOST'),
    'client_token'  => env('AKAMAI_CLIENT_TOKEN'),
    'access_token'  => env('AKAMAI_ACCESS_TOKEN'),
    'client_secret' => env('AKAMAI_CLIENT_SECRET'),
],
// app/Services/Akamai/AkamaiSigner.php
namespace App\Services\Akamai;

use Akamai\Open\EdgeGrid\Auth\Authentication;

class AkamaiSigner
{
    public function sign(string $method, string $path, string $query = '', array $headers = []): string
    {
        $auth = (new Authentication())
            ->setHost(config('services.akamai.host'))
            ->setClientToken(config('services.akamai.client_token'))
            ->setAccessToken(config('services.akamai.access_token'))
            ->setClientSecret(config('services.akamai.client_secret'));

        $auth->setHttpMethod(strtoupper($method))
             ->setPath($path)
             ->setQuery($query)
             ->setHeaders($headers ?: ['Host' => config('services.akamai.host')]);

        return $auth->createAuthHeader();
    }

    public function url(string $path, array $query = []): string
    {
        return 'https://' . config('services.akamai.host') . $path
            . ($query ? '?' . http_build_query($query, '', '&', PHP_QUERY_RFC3986) : '');
    }
}

Build a fresh Authentication per request rather than a singleton — see the nonce gotcha below. Object construction is cheap; a replayed nonce is not.

Pattern 2: Guzzle middleware (when you own the stack)

If you're using a dedicated Guzzle client for Akamai, sign as a middleware so callers never see it:

use Akamai\Open\EdgeGrid\Auth\Authentication;
use Psr\Http\Message\RequestInterface;

function akamaiSigner(callable $makeAuth): callable
{
    return fn (callable $handler) => function (RequestInterface $request, array $options) use ($handler, $makeAuth) {
        /** @var Authentication $auth */
        $auth = $makeAuth();

        $auth->setHttpMethod($request->getMethod())
             ->setPath($request->getUri()->getPath())
             ->setQuery($request->getUri()->getQuery())
             ->setHeaders(['Host' => $request->getUri()->getHost()]);

        if ($request->getBody()->getSize() > 0) {
            $auth->setBody((string) $request->getBody());
        }

        return $handler(
            $request->withHeader('Authorization', $auth->createAuthHeader()),
            $options
        );
    };
}

The middleware runs after the URI and body are final, which is exactly the ordering constraint the scheme imposes.

Pattern 3: A thin Akamai client wrapper

Most teams end up with a AkamaiClient that exposes the handful of endpoints they actually use, with signing folded into a private send():

public function contracts(): array
{
    return $this->send('GET', '/papi/v1/contracts')['contracts']['items'];
}

public function invalidate(array $objects, string $network = 'staging'): array
{
    $body = ['objects' => $objects];
    return $this->send('POST', "/ccu/v3/invalidate/url/$network", '', $body);
}

private function send(string $method, string $path, string $query = '', ?array $json = null): array
{
    $headers = ['Host' => config('services.akamai.host')];
    $auth = $this->auth->sign($method, $path, $query, $headers);

    $request = Http::withHeaders([
        'Authorization' => $auth,
        'Accept'        => 'application/json',
    ]);

    if ($json !== null) {
        $request = $request->asJson()->withBody(json_encode($json), 'application/json');
    }

    return $request->send($method, $this->signer->url($path, $query ? [$query] : []))
        ->throw()
        ->json();
}

Note the asymmetry: Accept is sent but not signed. That is fine and is the recommended default — sign nothing but Host unless you have a reason.

Pattern 4: Long-running jobs and queues

Signing is stateless and CPU-trivial (a SHA-256 and an HMAC). It is safe inside queue workers and Artisan commands. What is not safe is building one Authentication object in a service provider and reusing it across thousands of jobs — the timestamp and nonce go stale. Construct per request.


Gotchas and Tips

This is a signing library, not a client. There is no GET helper, no retry, no rate-limit handling. Akamai's APIs return 429 with a Retry-After header under load; you have to implement backoff yourself. If you want the batteries-included path there is a companion Guzzle-based client package in the same monorepo — pull it in instead of hand-rolling unless you specifically need non-Guzzle HTTP.

The class lives under an Auth sub-namespace. If you're porting code from the old monolithic Akamai\Open\EdgeGrid\Client era, Akamai\Open\EdgeGrid\Authentication became Akamai\Open\EdgeGrid\Auth\Authentication in the split package. Copy-pasted snippets from old blog posts will fail with a class-not-found that looks like a broken install.

Sign the URL you actually send. /papi/v1/contracts and /papi/v1/contracts/ are different strings and different signatures. So are ?groupId=1&contractId=2 and ?contractId=2&groupId=1. If any layer in front of you (a proxy, a base-URI helper, Laravel's http_build_query default of PHP_QUERY_RFC1738) rewrites the query, the signature breaks. Normalize query ordering once, in one place, and use RFC3986 encoding.

Do not sign headers you don't send. The canonical header block lists exactly the headers you pass to setHeaders(). Ask for Host only and you'll rarely break. Add Content-Type and forget to set it on a GET and every request 403s with a signature mismatch that looks like a credential problem.

Host must be the Akamai hostname, not an IP or a proxy alias. If you terminate TLS somewhere that rewrites Host, signing must happen on the far side or not at all.

Timestamps expire in ~30 seconds. Queue jobs that sign a request and then wait (rate limiter, sleep, chunked batch) will 403 with Timestamp outside the authentication window. Sign immediately before the send, never at the top of the method.

Nonces are single-use and replay-protected. Reusing an Authentication instance across loop iterations without regenerating the nonce will start failing after the first request. Fastest fix: new Authentication() inside the loop. If you must reuse, call setNonce(null) (or a fresh random value) and setTimestamp(null) before each createAuthHeader().

Bodies over 2048 bytes are hashed partially. EdgeGrid's content hash covers only the first max-body-size bytes (2048 by default). If you set the body on one side and let the server hash a differently-truncated body — usually because a middleware re-encoded JSON — you get a signature mismatch on POST/PUT only, which is a maddening symptom. Set the body once, after all encoding, and don't touch it again.

Debugging a 403. The response body from Akamai includes the signature it computed versus the one it received. Diff them, then walk the canonical request by hand: method, scheme, host, path, sorted query, then the canonical headers, then the signed-header names line, then the body hash. In practice 90% of failures are (a) query ordering, (b) a signed header you didn't send, or (c) a trailing slash.

Deterministic tests. Pass a fixed nonce and timestamp to get a stable header, then assert against a recorded Authorization string. Build the fixture from a real request you know succeeded, not from your own output — a signing library tested against itself proves nothing.

Log the unsigned request, never the secret. client_secret goes into the HMAC and never on the wire, but it's easy to leak it into an exception dump of your config array. Use config()->set() scoping or a #[SensitiveParameter]-style guard when logging near the signer.

The split repo is a mirror. Issues, PRs, and the changelog live on the monorepo (AkamaiOPEN-edgegrid-php). File bugs there. With ~14 stars and no dependents the package gets little independent scrutiny, so pin a version in composer.json and read the diff on upgrade — the surface is small enough to audit in a sitting.

Extension points are minimal, and that's the point. The public API is setters plus createAuthHeader(). There is no middleware chain, no PSR-18 adapter, no event hook. If you need correlation IDs, telemetry, or retries, wrap it — don't fork it.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource