Integrate API Platform security

Edit on GitHub

This document describes how to integrate Symfony’s SecurityBundle with the API Platform to enable authentication and authorization for your API resources.

Prerequisites

  • API Platform is already integrated as described in Integrate API Platform.
  • The spryker/api-platform module version 1.0.0 or later is installed.

1. Register the SecurityBundle

Add SecurityBundle to the bundles.php file for each Glue application where you want to enable security.

For Glue application

config/Glue/bundles.php

<?php

declare(strict_types = 1);

use ApiPlatform\Symfony\Bundle\ApiPlatformBundle;
use Spryker\ApiPlatform\SprykerApiPlatformBundle;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
use Symfony\Bundle\TwigBundle\TwigBundle;

return [
    FrameworkBundle::class => ['all' => true],
    SecurityBundle::class => ['all' => true],
    TwigBundle::class => ['all' => true],
    ApiPlatformBundle::class => ['all' => true],
    SprykerApiPlatformBundle::class => ['all' => true],
];

For GlueStorefront application

config/GlueStorefront/bundles.php

<?php

declare(strict_types = 1);

use ApiPlatform\Symfony\Bundle\ApiPlatformBundle;
use Spryker\ApiPlatform\SprykerApiPlatformBundle;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
use Symfony\Bundle\TwigBundle\TwigBundle;

return [
    FrameworkBundle::class => ['all' => true],
    SecurityBundle::class => ['all' => true],
    TwigBundle::class => ['all' => true],
    ApiPlatformBundle::class => ['all' => true],
    SprykerApiPlatformBundle::class => ['all' => true],
];

For GlueBackend application

config/GlueBackend/bundles.php

<?php

declare(strict_types = 1);

use ApiPlatform\Symfony\Bundle\ApiPlatformBundle;
use Spryker\ApiPlatform\SprykerApiPlatformBundle;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
use Symfony\Bundle\TwigBundle\TwigBundle;

return [
    FrameworkBundle::class => ['all' => true],
    SecurityBundle::class => ['all' => true],
    TwigBundle::class => ['all' => true],
    ApiPlatformBundle::class => ['all' => true],
    SprykerApiPlatformBundle::class => ['all' => true],
];
Bundle ordering

SecurityBundle must be registered before ApiPlatformBundle and SprykerApiPlatformBundle so that the security services are available when API Platform compiles its configuration.

2. Configure the security firewall

Create a security.php configuration file for each Glue application. This file defines the authentication provider, firewall, and default access control.

For Glue application

config/Glue/packages/security.php

<?php

declare(strict_types=1);

use Spryker\ApiPlatform\Security\ApiUserProvider;
use Spryker\ApiPlatform\Security\GlueAuthenticationEntryPoint;
use Spryker\ApiPlatform\Security\OauthAuthenticator;
use Symfony\Config\SecurityConfig;

return static function (SecurityConfig $security): void {
    $security->provider('api_oauth_provider')
        ->id(ApiUserProvider::class);

    $security->firewall('main')
        ->lazy(true)
        ->stateless(true)
        ->provider('api_oauth_provider')
        ->customAuthenticators([OauthAuthenticator::class])
        ->entryPoint(GlueAuthenticationEntryPoint::class);

    // Public by default - individual resources use security expressions for authorization
    $security->accessControl()
        ->path('^/')
        ->roles(['PUBLIC_ACCESS']);
};

For GlueStorefront application

config/GlueStorefront/packages/security.php

Use the same configuration as above.

For GlueBackend application

config/GlueBackend/packages/security.php

Use the same configuration as above.

Configuration explained

Setting Description
provider('api_oauth_provider') Registers the user provider that builds ApiUser objects from validated JWT claims.
firewall('main')->lazy(true) The authenticator is only instantiated when a route requires authentication, reducing overhead for public endpoints.
firewall('main')->stateless(true) Disables session-based authentication. Every request must include its own Bearer token.
customAuthenticators([OauthAuthenticator::class]) Registers the Spryker OAuth authenticator that validates Bearer tokens using the local OAuth infrastructure.
entryPoint(GlueAuthenticationEntryPoint::class) Returns the standard 403 Missing access token. error when an unauthenticated request hits a protected resource.
accessControl()->roles(['PUBLIC_ACCESS']) Grants public access to all paths by default. Individual resources opt in to authentication using security expressions.

3. Add security expressions to resources

After the SecurityBundle is configured, you can protect resources using security expressions in your YAML resource schemas, either for an entire resource or for specific operations. For the expression syntax, available variables, and examples, see API Platform security.

Regenerate resources

After adding security expressions, regenerate your API resources:

docker/sdk cli glue api:generate

4. Optional: Enable Persistent ACL for the Backend API

For tokens of Back Office and merchant users, the Backend API resolves the user behind the token and makes it the acting user of the request. To have Persistent ACL scope the requests of merchant users to their merchant the same way the Merchant Portal does, register the following plugins. Back Office users are not scoped, as in the Back Office. The scoping applies to API Platform resources only: legacy Glue resources run without an acting user, so Persistent ACL stays disabled for them.

PLUGIN SPECIFICATION PREREQUISITES NAMESPACE
AclEntityApplicationPlugin Enables Persistent ACL for the Backend API application. Spryker\Glue\AclEntity\Plugin\Application
NoCurrentMerchantUserAclEntityDisablerPlugin Disables Persistent ACL unless the acting user is a merchant user, so that Back Office users, userless requests like the token endpoint, and public endpoints are not filtered. Spryker\Zed\MerchantUser\Communication\Plugin\AclEntity

src/Pyz/Glue/GlueBackendApiApplication/GlueBackendApiApplicationDependencyProvider.php

<?php

namespace Pyz\Glue\GlueBackendApiApplication;

use Spryker\Glue\GlueBackendApiApplication\GlueBackendApiApplicationDependencyProvider as SprykerGlueBackendApiApplicationDependencyProvider;
use Spryker\Glue\AclEntity\Plugin\Application\AclEntityApplicationPlugin;

class GlueBackendApiApplicationDependencyProvider extends SprykerGlueBackendApiApplicationDependencyProvider
{
    /**
     * @return array<\Spryker\Shared\ApplicationExtension\Dependency\Plugin\ApplicationPluginInterface>
     */
    protected function getApplicationPlugins(): array
    {
        return [
            new AclEntityApplicationPlugin(),
        ];
    }
}

src/Pyz/Zed/AclEntity/AclEntityDependencyProvider.php

<?php

namespace Pyz\Zed\AclEntity;

use Spryker\Zed\AclEntity\AclEntityDependencyProvider as SprykerAclEntityDependencyProvider;
use Spryker\Zed\MerchantUser\Communication\Plugin\AclEntity\NoCurrentMerchantUserAclEntityDisablerPlugin;

class AclEntityDependencyProvider extends SprykerAclEntityDependencyProvider
{
    /**
     * @return array<\Spryker\Zed\AclEntityExtension\Dependency\Plugin\AclEntityDisablerPluginInterface>
     */
    protected function getAclEntityDisablerPlugins(): array
    {
        return [
            new NoCurrentMerchantUserAclEntityDisablerPlugin(),
        ];
    }
}
Verification

Authenticate as a merchant user and request a resource that is scoped by Persistent ACL, for example, GET /merchant-profile. Make sure the response contains only the data of the merchant the user is assigned to, that a Back Office user still reads the data of every merchant, and that POST /token still succeeds without an Authorization header.

5. Clear caches

Clear application caches after configuration changes:

docker/sdk cli console cache:clear

Verification

Verify SecurityBundle is registered

Check that the security services are available:

docker/sdk cli glue debug:container SecurityBundle

Test authentication

Send a request without a token to a protected resource — it should return 403 Forbidden with the Missing access token. error:

curl -s https://glue-storefront.your-domain/customers/DE--1 | jq .

Send a request with a valid Bearer token:

curl -s -H "Authorization: Bearer <your-jwt-token>" \
  https://glue-storefront.your-domain/customers/DE--1 | jq .

Compile-time validation

If you add security expressions to resource schemas but forget to register the SecurityBundle, the application throws an error at compile time:

InvalidArgumentException: The following API resource schemas use security expressions
but SecurityBundle is not registered: customers, orders. Register SecurityBundle in
your bundles.php to enable security expression evaluation.

This validation is performed by the SecurityServiceRegistrationPass compiler pass.

Next steps