Skip to content

Latest commit

 

History

History
267 lines (196 loc) · 6.61 KB

File metadata and controls

267 lines (196 loc) · 6.61 KB

Api key usage

Basics

In most simplest form you can define keys right in the config. This is useful when you collaborate between your own applications or for testing purposes:

damax_api_auth:
    api_key:
        storage:
            app_one: '%env(API_KEY_APP_ONE)%'
            app_two: '%env(API_KEY_APP_TWO)%'

Security

Two services are registered: damax.api_auth.api_key.user_provider and damax.api_auth.api_key.authenticator. Consider below security.yml configuration:

security:
    encoders:
        Symfony\Component\Security\Core\User\User: plaintext

    providers:
        damax_api:
            id: damax.api_auth.api_key.user_provider

    firewalls:
        main:
            pattern:   ^/api/
            anonymous: false
            stateless: true
            guard:     { provider: damax_api, authenticator: damax.api_auth.api_key.authenticator }

    access_control:
        - { path: ^/api/, role: ROLE_API }

All routes with /api/ prefix are now guarded by API key authentication.

Api user

It is possible to distinguish API users if needed, e.g. in controller:

// ....

/**
 * @Route("/api")
 */
class TestController extends Controller
{
    /**
     * @Route("/run")
     */
    public function runAction(): Response
    {
        $user = $this->getUser();

        if ('app_one' === $user->getUsername()) {
            $result = $this->appOneAction();
        } elseif ('app_two' === $user->getUsername()) {
            $result = $this->appTwoAction();
        } else {
            $result = $this->defaultAction();
        }

        return JsonResponse::create($result);
    }
}

Storage

Api keys can be stored in Redis or database accessed through Doctrine.

Redis

Store keys in Redis with api: prefix.

damax_api_auth:
    api_key:
        storage:
            - { type: redis, key_prefix: 'api:' }

Use multiple storage drivers:

damax_api_auth:
    api_key:
        storage:
            - { type: redis, redis_client_id: snc_redis.api_cache }
            - { type: redis, key_prefix: 'api:', redis_client_id: snc_redis.default }

Lookup using snc_redis.api_cache client, if not found, then search for a key using snc_redis.default client.

Database

Doctrine storage configuration:

damax_api_auth:
    api_key:
        storage:
            - type: doctrine
              table_name: user_api_key
              fields: { key: id, identity: user_id, ttl: expires_at }

ttl field must be of type integer. Default fields values are: key, identity and ttl.

All in one

You can mix multiple storage types:

damax_api_auth:
    api_key:
        storage:
            - type: fixed
              tokens:
                  app_one: '%env(API_KEY_APP_ONE)%'
                  app_two: '%env(API_KEY_APP_TWO)%'
            - type: redis
              key_prefix: 'api:'
            - type: doctrine
              table_name: user_api_key
              fields: { key: id, identity: user_id, ttl: expires_at }

Above configuration does the following:

  • always grant access for app_one and app_two;
  • for others search in Redis;
  • if not found, then see if there is a match in user_api_key table with non-expired key.

Console

Test given key:

$ ./bin/console damax:api-auth:storage:lookup-key <key>

This will go through all the configured storage types. If found, it returns the identity behind the key and ttl.

Add or remove

The typical scenario is to use database with caching in Redis, where keys are disposable by their nature when ttl reaches zero. This is an easy way to grant a temporary access to your API without making changes in the database.

That being said, you need to define which storage is writable i.e. add to or remove keys from:

damax_api_auth:
    api_key:
        storage:
            # ...
            - { type: redis, key_prefix: 'api:', writable: true }
            # ...

To add a new key, please run:

$ ./bin/console damax:api-auth:storage:add-key john.doe@domain.abc 2hours

The new key is now available for 2 hours. Default value: 1 week.

To remove a key:

$ ./bin/console damax:api-auth:storage:remove-key <key>

Only one writable storage can be defined. fixed type can not be writable.

Extractors

By default API key is expected to be found in Authorization header with Token prefix.

Example cURL command:

$ curl -H "Authorization: Token secret" https://domain.abc/api/run

To fine tune extractors to search for a key in cookie, query or header, consider the following:

damax_api_auth:
    api_key:
        extractors:
            - { type: query, name: api_key }
            - { type: query, name: apikey }
            - { type: cookie, name: api_key }
            - { type: header, name: 'X-Auth-Token' }
            - { type: header, name: 'X-Auth', prefix: Token }

All the following cURL requests are accepted for authentication:

$ curl https://domain.abc/api/run?api_key=secret
$ curl https://domain.abc/api/run?apikey=secret
$ curl --cookie "api_key=secret" https://domain.abc/api/run
$ curl -H "X-Auth-Token: secret" https://domain.abc/api/run
$ curl -H "X-Auth: Token secret" https://domain.abc/api/run

Error response

When user is not authenticated or when access is denied the error response is returned with 401 and 403 status codes respectively. By default the response body is in standard Symfony format:

{
    "error: { "code": 401, "message": "Unathorized" }
}

You can customize error response by implementing ResponseFactory, register service in container and specify in config:

damax_api_auth:
    response_factory_service_id: my_response_factory_service

Custom user provider

If you want to store keys in your own way and load custom user implementation, then implement ApiKeyUserProvider:

use Damax\Bundle\ApiAuthBundle\Security\ApiKey\ApiKeyUserProvider;
use Damax\Bundle\ApiAuthBundle\Security\ApiKey\InvalidApiKey;

class UserProvider implements ApiKeyUserProvider
{
    private $repository;

    public function __construct(UserRepository $repository)
    {
        $this->repository = $repository;
    }

    // ...

    public function loadUserByApiKey(string $key): UserInterface
    {
        if (null === $user = $this->repository->byApiKey($key)) {
            throw new InvalidApiKey();
        }

        return $user;
    }
}

Register it in container and update security.yml accordingly.

Next

Read next how to authenticate with JWT.