Skip to content

Secure a GraphQL API with JWT authentication🔗

How to setup an authentication by JWT on a GraphQL API using overblog/GraphQLBundle and lexik/jwt-authentication-bundle.

Installation🔗

This cookbook asume you already have a working GraphQL api.

1
composer install lexik/jwt-authentication-bundle

Follow the documentation to generate and configure a public and a secret key.

Configuration🔗

Add a firewall on your GraphQL API;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# config/packages/security.yaml
security:
    # ...
    firewalls:
        api:
            pattern: ^/api
            provider: your_user_provider
            anonymous: ~
            stateless: true
            guard:
                authenticators:
                    - lexik_jwt_authentication.jwt_token_authenticator

If users must be authenticated on all your API, add the following access control:

1
2
3
4
5
# config/packages/security.yaml
security:
    # ...
    access_control:
        - { path: ^/api, roles: IS_AUTHENTICATED_FULLY }

If anonymous users can access some parts of your API:

1
2
3
4
5
# config/packages/security.yaml
security:
    # ...
    access_control:
        - { path: ^/api, roles: IS_AUTHENTICATED_ANONYMOUSLY }

Then require authentication on queries, mutations and fields by using the access or the public config:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# config/graphql/types/Query.types.yaml
Query:
    type: object
    config:
        fields:
            Foobar:
                type: "Foobar"
                resolve: '@=resolver("App\\Infra\\GraphQL\\Resolver\\FoobarResolver", [args])'
                access: '@=isAuthenticated()'
            Bar:
                type: "Bar"
                resolve: '@=resolver("App\\Infra\\GraphQL\\Resolver\\BarResolver", [args])'
                access: '@=service('security.authorization_checker').isGranted('ROLE_BAR')'
            Barfoo:
                type: "Barfoo"
                resolve: '@=resolver("App\\Infra\\GraphQL\\Resolver\\BarfooResolver", [args])'
                public: '@=isAuthenticated()'

Note: Unauthorized users can see fields with access condition but can access to it whereas they can't see at all fields with public condition. Be aware that objects must have at least one public field (so at least one query and one mutation).

See overblog bundle security documentation for more options.

Authentication will require client to provide a valid JWT token through the Authorization header:

1
Authorization: Bearer YOUR_JWT_TOKEN

Authentication🔗

You will need a way to provide valid JWT to your user.

The following example is an authentication by username and password providing JWT in case of success.

Create a route:

1
2
3
4
# config/routes.yaml
api_authentication:
    path: /api/authentication
    methods: [POST]

Then configure a firewall with a guard authenticator for this route:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# config/packages/security.yaml
security:
    firewalls:
        # ...
        api_authentication:
            pattern: ^/api/authentication
            provider: your_user_provider
            stateless: true
            guard:
                authenticators:
                    - App\Infra\Security\Guard\UsernamePasswordGuardAuthenticator

Note: This firewall must be defined before api's firewall.

The UsernamePasswordGuardAuthenticator authenticate the user based on the given username and password and return a JWT in response. Customize this authentication according to your needs.

GraphiQL🔗

If you are using GraphiQL through overblog/GraphiQLBundle you have to customize it a little in order to handle authentication.

First, add access control to GraphiQL:

1
2
3
4
5
6
# config/packages/security.yaml
security:
    # ...
    access_control:
        - { path: ^/api/graphiql, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/api, roles: IS_AUTHENTICATED_FULLY }

Note: GraphiQL is generaly not available in production, so it's fine to grant access to anonymous users. If your api has an access control, be sure to put graphiql access control before.

GraphiQL template allow us to override headers that will be sent to GraphQL, just what we want to add our authentication header.

Before, you need to create a Twig filter generating JWT from a username:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
namespace App\Infra\Twig\Extension;

use Lexik\Bundle\JWTAuthenticationBundle\Encoder\JWTEncoderInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;

class SecurityExtension extends AbstractExtension
{
    private JWTEncoderInterface $encoder;

    public function __construct(JWTEncoderInterface $encoder)
    {
        $this->encoder = $encoder;
    }

    public function getFilters()
    {
        return [
            new TwigFilter('jwt', function (string $username): string {
                return $this->encoder->encode(['username' => $username]);
            }),
        ];
    }
}

Then add the JWT to headers in the template:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{# template/api/graphiql.html.twig #}
{% extends '@OverblogGraphiQL/GraphiQL/index.html.twig' %}

{% block graphql_fetcher_headers %}
    headers = {
        "Accept": "application/json",
        "Content-Type": "application/json",
        "Authorization": "Bearer {{ app.request.query.get('token', app.request.query.get('username', 'admin@example.com')|jwt) }}",
    }
{% endblock graphql_fetcher_headers %}

Finally we will configure GraphiQL to use this template.

1
2
3
# config/packages/dev/graphiql.yaml
overblog_graphiql:
    template: 'api/graphiql.html.twig'

Usage🔗

With this template, you can set the token you want to use with the token parameter when accessing to GraphiQL:

1
/api/graphiql?token=YOUR_JWT_TOKEN

Or set the username parameter, a JWT will be generated for you:

1
/api/graphiql?username=maxime.colin@elao.com

Or use the default username (admin@example.com in our example):

1
/api/graphiql

Project references🔗


Last update: December 20, 2024