├── .env ├── .env.test ├── .gitignore ├── README.md ├── bin ├── console └── phpunit ├── composer.json ├── composer.lock ├── config ├── bootstrap.php ├── bundles.php ├── packages │ ├── cache.yaml │ ├── dev │ │ ├── debug.yaml │ │ ├── easy_log_handler.yaml │ │ ├── monolog.yaml │ │ ├── routing.yaml │ │ ├── swiftmailer.yaml │ │ └── web_profiler.yaml │ ├── doctrine.yaml │ ├── doctrine_migrations.yaml │ ├── framework.yaml │ ├── prod │ │ ├── doctrine.yaml │ │ └── monolog.yaml │ ├── routing.yaml │ ├── security.yaml │ ├── sensio_framework_extra.yaml │ ├── swiftmailer.yaml │ ├── test │ │ ├── framework.yaml │ │ ├── monolog.yaml │ │ ├── routing.yaml │ │ ├── swiftmailer.yaml │ │ ├── validator.yaml │ │ └── web_profiler.yaml │ ├── translation.yaml │ ├── twig.yaml │ └── validator.yaml ├── routes.yaml ├── routes │ ├── annotations.yaml │ └── dev │ │ ├── twig.yaml │ │ └── web_profiler.yaml └── services.yaml ├── phpunit.xml.dist ├── public └── index.php ├── src ├── Controller │ ├── .gitignore │ ├── Api │ │ ├── EntrypointController.php │ │ └── SecurityController.php │ └── FrontendController.php ├── DataFixtures │ └── AppFixtures.php ├── Entity │ ├── .gitignore │ └── User.php ├── Kernel.php ├── Migrations │ ├── .gitignore │ └── Version20190524150316.php ├── Repository │ ├── .gitignore │ └── UserRepository.php └── Security │ └── ApiAuthenticator.php ├── symfony.lock ├── templates ├── base.html.twig └── frontend │ └── index.html.twig ├── tests └── .gitignore └── translations └── .gitignore /.env: -------------------------------------------------------------------------------- 1 | # In all environments, the following files are loaded if they exist, 2 | # the later taking precedence over the former: 3 | # 4 | # * .env contains default values for the environment variables needed by the app 5 | # * .env.local uncommitted file with local overrides 6 | # * .env.$APP_ENV committed environment-specific defaults 7 | # * .env.$APP_ENV.local uncommitted environment-specific overrides 8 | # 9 | # Real environment variables win over .env files. 10 | # 11 | # DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES. 12 | # 13 | # Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2). 14 | # https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration 15 | 16 | ###> symfony/framework-bundle ### 17 | APP_ENV=dev 18 | APP_SECRET=4cfd31cd082e0e7b576b0feefec1f09c 19 | #TRUSTED_PROXIES=127.0.0.1,127.0.0.2 20 | #TRUSTED_HOSTS='^localhost|example\.com$' 21 | ###< symfony/framework-bundle ### 22 | 23 | ###> doctrine/doctrine-bundle ### 24 | # Format described at http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url 25 | # For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db" 26 | # Configure your db driver and server_version in config/packages/doctrine.yaml 27 | DATABASE_URL=pgsql://ubuntu@postgres-10.lxd/no-jwt 28 | ###< doctrine/doctrine-bundle ### 29 | 30 | ###> symfony/swiftmailer-bundle ### 31 | # For Gmail as a transport, use: "gmail://username:password@localhost" 32 | # For a generic SMTP server, use: "smtp://localhost:25?encryption=&auth_mode=" 33 | # Delivery is disabled by default via "null://localhost" 34 | MAILER_URL=null://localhost 35 | ###< symfony/swiftmailer-bundle ### 36 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | # define your env variables for the test env here 2 | KERNEL_CLASS='App\Kernel' 3 | APP_SECRET='s$cretf0rt3st' 4 | SYMFONY_DEPRECATIONS_HELPER=999999 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ###> symfony/framework-bundle ### 3 | /.env.local 4 | /.env.local.php 5 | /.env.*.local 6 | /public/bundles/ 7 | /var/ 8 | /vendor/ 9 | ###< symfony/framework-bundle ### 10 | 11 | ###> symfony/phpunit-bridge ### 12 | .phpunit 13 | /phpunit.xml 14 | ###< symfony/phpunit-bridge ### 15 | 16 | ###> symfony/web-server-bundle ### 17 | /.web-server-pid 18 | ###< symfony/web-server-bundle ### 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # You don't need JWT 2 | 3 | This application is a demo to show how we can avoid JWT with Symfony. 4 | 5 | It's better to read the [blog post](https://jolicode.com/blog/why-you-dont-need-jwt) 6 | before playing with it. 7 | 8 | ## Installation 9 | 10 | You can create a `.env.local` file, and override env var defined in `.env` in it. 11 | 12 | composer install 13 | bin/console doctrine:database:create 14 | bin/console doctrine:migration:migration 15 | bin/console doctrine:fixture:load 16 | 17 | ## Key files: 18 | 19 | * [Security Controller to get a token](./src/Controller/Api/SecurityController.php) 20 | * [`security.yaml`](./config/packages/security.yaml) 21 | * [The user class](./src/Entity/User.php) 22 | * [A very simple JS application](./templates/frontend/index.html.twig) 23 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | getParameterOption(['--env', '-e'], null, true)) { 23 | putenv('APP_ENV='.$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $env); 24 | } 25 | 26 | if ($input->hasParameterOption('--no-debug', true)) { 27 | putenv('APP_DEBUG='.$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0'); 28 | } 29 | 30 | require dirname(__DIR__).'/config/bootstrap.php'; 31 | 32 | if ($_SERVER['APP_DEBUG']) { 33 | umask(0000); 34 | 35 | if (class_exists(Debug::class)) { 36 | Debug::enable(); 37 | } 38 | } 39 | 40 | $kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']); 41 | $application = new Application($kernel); 42 | $application->run($input); 43 | -------------------------------------------------------------------------------- /bin/phpunit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | =1.2) 9 | if (is_array($env = @include dirname(__DIR__).'/.env.local.php')) { 10 | $_ENV += $env; 11 | } elseif (!class_exists(Dotenv::class)) { 12 | throw new RuntimeException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.'); 13 | } else { 14 | // load all the .env files 15 | (new Dotenv(false))->loadEnv(dirname(__DIR__).'/.env'); 16 | } 17 | 18 | $_SERVER += $_ENV; 19 | $_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev'; 20 | $_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? 'prod' !== $_SERVER['APP_ENV']; 21 | $_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = (int) $_SERVER['APP_DEBUG'] || filter_var($_SERVER['APP_DEBUG'], FILTER_VALIDATE_BOOLEAN) ? '1' : '0'; 22 | -------------------------------------------------------------------------------- /config/bundles.php: -------------------------------------------------------------------------------- 1 | ['all' => true], 5 | Doctrine\Bundle\DoctrineCacheBundle\DoctrineCacheBundle::class => ['all' => true], 6 | Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true], 7 | Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], 8 | Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], 9 | Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], 10 | Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle::class => ['all' => true], 11 | Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], 12 | Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], 13 | Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], 14 | Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true], 15 | Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], 16 | Symfony\Bundle\WebServerBundle\WebServerBundle::class => ['dev' => true], 17 | Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true], 18 | ]; 19 | -------------------------------------------------------------------------------- /config/packages/cache.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | cache: 3 | # Put the unique name of your app here: the prefix seed 4 | # is used to compute stable namespaces for cache keys. 5 | #prefix_seed: your_vendor_name/app_name 6 | 7 | # The app cache caches to the filesystem by default. 8 | # Other options include: 9 | 10 | # Redis 11 | #app: cache.adapter.redis 12 | #default_redis_provider: redis://localhost 13 | 14 | # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) 15 | #app: cache.adapter.apcu 16 | 17 | # Namespaced pools use the above "app" backend by default 18 | #pools: 19 | #my.dedicated.cache: ~ 20 | -------------------------------------------------------------------------------- /config/packages/dev/debug.yaml: -------------------------------------------------------------------------------- 1 | debug: 2 | # Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser. 3 | # See the "server:dump" command to start a new server. 4 | dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%" 5 | -------------------------------------------------------------------------------- /config/packages/dev/easy_log_handler.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | EasyCorp\EasyLog\EasyLogHandler: 3 | public: false 4 | arguments: ['%kernel.logs_dir%/%kernel.environment%.log'] 5 | 6 | #// FIXME: How to add this configuration automatically without messing up with the monolog configuration? 7 | #monolog: 8 | # handlers: 9 | # buffered: 10 | # type: buffer 11 | # handler: easylog 12 | # channels: ['!event'] 13 | # level: debug 14 | # easylog: 15 | # type: service 16 | # id: EasyCorp\EasyLog\EasyLogHandler 17 | -------------------------------------------------------------------------------- /config/packages/dev/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: stream 5 | path: "%kernel.logs_dir%/%kernel.environment%.log" 6 | level: debug 7 | channels: ["!event"] 8 | # uncomment to get logging in your browser 9 | # you may have to allow bigger header sizes in your Web server configuration 10 | #firephp: 11 | # type: firephp 12 | # level: info 13 | #chromephp: 14 | # type: chromephp 15 | # level: info 16 | console: 17 | type: console 18 | process_psr_3_messages: false 19 | channels: ["!event", "!doctrine", "!console"] 20 | -------------------------------------------------------------------------------- /config/packages/dev/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | strict_requirements: true 4 | -------------------------------------------------------------------------------- /config/packages/dev/swiftmailer.yaml: -------------------------------------------------------------------------------- 1 | # See https://symfony.com/doc/current/email/dev_environment.html 2 | swiftmailer: 3 | # send all emails to a specific address 4 | #delivery_addresses: ['me@example.com'] 5 | -------------------------------------------------------------------------------- /config/packages/dev/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler: 2 | toolbar: true 3 | intercept_redirects: false 4 | 5 | framework: 6 | profiler: { only_exceptions: false } 7 | -------------------------------------------------------------------------------- /config/packages/doctrine.yaml: -------------------------------------------------------------------------------- 1 | doctrine: 2 | dbal: 3 | url: '%env(resolve:DATABASE_URL)%' 4 | orm: 5 | auto_generate_proxy_classes: true 6 | naming_strategy: doctrine.orm.naming_strategy.underscore 7 | auto_mapping: true 8 | mappings: 9 | App: 10 | is_bundle: false 11 | type: annotation 12 | dir: '%kernel.project_dir%/src/Entity' 13 | prefix: 'App\Entity' 14 | alias: App 15 | -------------------------------------------------------------------------------- /config/packages/doctrine_migrations.yaml: -------------------------------------------------------------------------------- 1 | doctrine_migrations: 2 | dir_name: '%kernel.project_dir%/src/Migrations' 3 | # namespace is arbitrary but should be different from App\Migrations 4 | # as migrations classes should NOT be autoloaded 5 | namespace: DoctrineMigrations 6 | -------------------------------------------------------------------------------- /config/packages/framework.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | secret: '%env(APP_SECRET)%' 3 | #default_locale: en 4 | #csrf_protection: true 5 | #http_method_override: true 6 | 7 | # Enables session support. Note that the session will ONLY be started if you read or write from it. 8 | # Remove or comment this section to explicitly disable session support. 9 | session: 10 | handler_id: ~ 11 | cookie_secure: auto 12 | cookie_samesite: lax 13 | 14 | #esi: true 15 | #fragments: true 16 | php_errors: 17 | log: true 18 | -------------------------------------------------------------------------------- /config/packages/prod/doctrine.yaml: -------------------------------------------------------------------------------- 1 | doctrine: 2 | orm: 3 | auto_generate_proxy_classes: false 4 | metadata_cache_driver: 5 | type: service 6 | id: doctrine.system_cache_provider 7 | query_cache_driver: 8 | type: service 9 | id: doctrine.system_cache_provider 10 | result_cache_driver: 11 | type: service 12 | id: doctrine.result_cache_provider 13 | 14 | services: 15 | doctrine.result_cache_provider: 16 | class: Symfony\Component\Cache\DoctrineProvider 17 | public: false 18 | arguments: 19 | - '@doctrine.result_cache_pool' 20 | doctrine.system_cache_provider: 21 | class: Symfony\Component\Cache\DoctrineProvider 22 | public: false 23 | arguments: 24 | - '@doctrine.system_cache_pool' 25 | 26 | framework: 27 | cache: 28 | pools: 29 | doctrine.result_cache_pool: 30 | adapter: cache.app 31 | doctrine.system_cache_pool: 32 | adapter: cache.system 33 | -------------------------------------------------------------------------------- /config/packages/prod/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: fingers_crossed 5 | action_level: error 6 | handler: nested 7 | excluded_404s: 8 | # regex: exclude all 404 errors from the logs 9 | - ^/ 10 | nested: 11 | type: stream 12 | path: "%kernel.logs_dir%/%kernel.environment%.log" 13 | level: debug 14 | console: 15 | type: console 16 | process_psr_3_messages: false 17 | channels: ["!event", "!doctrine"] 18 | deprecation: 19 | type: stream 20 | path: "%kernel.logs_dir%/%kernel.environment%.deprecations.log" 21 | deprecation_filter: 22 | type: filter 23 | handler: deprecation 24 | max_level: info 25 | channels: ["php"] 26 | -------------------------------------------------------------------------------- /config/packages/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | strict_requirements: ~ 4 | utf8: true 5 | -------------------------------------------------------------------------------- /config/packages/security.yaml: -------------------------------------------------------------------------------- 1 | security: 2 | encoders: 3 | App\Entity\User: 4 | algorithm: auto 5 | 6 | providers: 7 | users: 8 | entity: 9 | class: App\Entity\User 10 | property: email 11 | firewalls: 12 | dev: 13 | pattern: ^/(_(profiler|wdt)|css|images|js)/ 14 | security: false 15 | 16 | api_login: 17 | pattern: ^/api/login$ 18 | anonymous: false 19 | stateless: true 20 | json_login: 21 | check_path: api_login 22 | 23 | api: 24 | pattern: ^/api 25 | anonymous: false 26 | stateless: true 27 | guard: 28 | authenticators: 29 | - App\Security\ApiAuthenticator 30 | 31 | access_control: 32 | # - { path: ^/admin, roles: ROLE_ADMIN } 33 | # - { path: ^/profile, roles: ROLE_USER } 34 | -------------------------------------------------------------------------------- /config/packages/sensio_framework_extra.yaml: -------------------------------------------------------------------------------- 1 | sensio_framework_extra: 2 | router: 3 | annotations: false 4 | -------------------------------------------------------------------------------- /config/packages/swiftmailer.yaml: -------------------------------------------------------------------------------- 1 | swiftmailer: 2 | url: '%env(MAILER_URL)%' 3 | spool: { type: 'memory' } 4 | -------------------------------------------------------------------------------- /config/packages/test/framework.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | test: true 3 | session: 4 | storage_id: session.storage.mock_file 5 | -------------------------------------------------------------------------------- /config/packages/test/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: stream 5 | path: "%kernel.logs_dir%/%kernel.environment%.log" 6 | level: debug 7 | channels: ["!event"] 8 | -------------------------------------------------------------------------------- /config/packages/test/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | strict_requirements: true 4 | -------------------------------------------------------------------------------- /config/packages/test/swiftmailer.yaml: -------------------------------------------------------------------------------- 1 | swiftmailer: 2 | disable_delivery: true 3 | -------------------------------------------------------------------------------- /config/packages/test/validator.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | validation: 3 | # As of Symfony 4.3 you can disable the NotCompromisedPassword Validator 4 | # disable_not_compromised_password: true 5 | -------------------------------------------------------------------------------- /config/packages/test/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler: 2 | toolbar: false 3 | intercept_redirects: false 4 | 5 | framework: 6 | profiler: { collect: false } 7 | -------------------------------------------------------------------------------- /config/packages/translation.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | default_locale: '%locale%' 3 | translator: 4 | default_path: '%kernel.project_dir%/translations' 5 | fallbacks: 6 | - '%locale%' 7 | -------------------------------------------------------------------------------- /config/packages/twig.yaml: -------------------------------------------------------------------------------- 1 | twig: 2 | default_path: '%kernel.project_dir%/templates' 3 | debug: '%kernel.debug%' 4 | strict_variables: '%kernel.debug%' 5 | -------------------------------------------------------------------------------- /config/packages/validator.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | validation: 3 | email_validation_mode: html5 4 | -------------------------------------------------------------------------------- /config/routes.yaml: -------------------------------------------------------------------------------- 1 | #index: 2 | # path: / 3 | # controller: App\Controller\DefaultController::index 4 | -------------------------------------------------------------------------------- /config/routes/annotations.yaml: -------------------------------------------------------------------------------- 1 | controllers: 2 | resource: ../../src/Controller/ 3 | type: annotation 4 | -------------------------------------------------------------------------------- /config/routes/dev/twig.yaml: -------------------------------------------------------------------------------- 1 | _errors: 2 | resource: '@TwigBundle/Resources/config/routing/errors.xml' 3 | prefix: /_error 4 | -------------------------------------------------------------------------------- /config/routes/dev/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler_wdt: 2 | resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml' 3 | prefix: /_wdt 4 | 5 | web_profiler_profiler: 6 | resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml' 7 | prefix: /_profiler 8 | -------------------------------------------------------------------------------- /config/services.yaml: -------------------------------------------------------------------------------- 1 | # This file is the entry point to configure your own services. 2 | # Files in the packages/ subdirectory configure your dependencies. 3 | 4 | # Put parameters here that don't need to change on each machine where the app is deployed 5 | # https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration 6 | parameters: 7 | locale: 'en' 8 | 9 | services: 10 | # default configuration for services in *this* file 11 | _defaults: 12 | autowire: true # Automatically injects dependencies in your services. 13 | autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. 14 | 15 | # makes classes in src/ available to be used as services 16 | # this creates a service per class whose id is the fully-qualified class name 17 | App\: 18 | resource: '../src/*' 19 | exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}' 20 | 21 | # controllers are imported separately to make sure services can be injected 22 | # as action arguments even if you don't extend any base controller class 23 | App\Controller\: 24 | resource: '../src/Controller' 25 | tags: ['controller.service_arguments'] 26 | 27 | # add more service definitions when explicit configuration is needed 28 | # please note that last definitions always *replace* previous ones 29 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | tests 21 | 22 | 23 | 24 | 25 | 26 | src 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | handle($request); 26 | $response->send(); 27 | $kernel->terminate($request, $response); 28 | -------------------------------------------------------------------------------- /src/Controller/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jolicode/symfony-jwt-article/3dc572628b0cb2fb9d949a54dbdbe79a64a027b9/src/Controller/.gitignore -------------------------------------------------------------------------------- /src/Controller/Api/EntrypointController.php: -------------------------------------------------------------------------------- 1 | getUser(); 17 | 18 | return new JsonResponse([ 19 | 'apiToken' => $user->getApiToken(), 20 | 'roles' => $user->getRoles(), 21 | 'email' => $user->getEmail(), 22 | ]); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Controller/FrontendController.php: -------------------------------------------------------------------------------- 1 | render('frontend/index.html.twig'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/DataFixtures/AppFixtures.php: -------------------------------------------------------------------------------- 1 | setEmail('lyrixx@lyrixx.info'); 15 | // password is: password 16 | $user->setPassword('$argon2i$v=19$m=1024,t=2,p=2$R0F6cDBuSk45bDRGWVZvZw$X5pV2LtbgYePqM+WbdsCXrES/jq6hc3XCjBZV3bvXmU'); 17 | 18 | $manager->persist($user); 19 | 20 | $manager->flush(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Entity/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jolicode/symfony-jwt-article/3dc572628b0cb2fb9d949a54dbdbe79a64a027b9/src/Entity/.gitignore -------------------------------------------------------------------------------- /src/Entity/User.php: -------------------------------------------------------------------------------- 1 | apiToken = bin2hex(random_bytes(20)); 45 | } 46 | 47 | public function getId(): ?int 48 | { 49 | return $this->id; 50 | } 51 | 52 | public function getEmail(): ?string 53 | { 54 | return $this->email; 55 | } 56 | 57 | public function setEmail(string $email) 58 | { 59 | $this->email = $email; 60 | } 61 | 62 | public function getUsername(): string 63 | { 64 | return (string) $this->email; 65 | } 66 | 67 | public function getRoles(): array 68 | { 69 | $roles = $this->roles; 70 | $roles[] = 'ROLE_USER'; 71 | 72 | return array_unique($roles); 73 | } 74 | 75 | public function getPassword(): string 76 | { 77 | return (string) $this->password; 78 | } 79 | 80 | public function setPassword(string $password) 81 | { 82 | $this->password = $password; 83 | } 84 | 85 | public function getSalt() 86 | { 87 | } 88 | 89 | public function eraseCredentials() 90 | { 91 | } 92 | 93 | public function getApiToken(): ?string 94 | { 95 | return $this->apiToken; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Kernel.php: -------------------------------------------------------------------------------- 1 | getProjectDir().'/config/bundles.php'; 21 | foreach ($contents as $class => $envs) { 22 | if ($envs[$this->environment] ?? $envs['all'] ?? false) { 23 | yield new $class(); 24 | } 25 | } 26 | } 27 | 28 | public function getProjectDir(): string 29 | { 30 | return \dirname(__DIR__); 31 | } 32 | 33 | protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void 34 | { 35 | $container->addResource(new FileResource($this->getProjectDir().'/config/bundles.php')); 36 | $container->setParameter('container.dumper.inline_class_loader', true); 37 | $confDir = $this->getProjectDir().'/config'; 38 | 39 | $loader->load($confDir.'/{packages}/*'.self::CONFIG_EXTS, 'glob'); 40 | $loader->load($confDir.'/{packages}/'.$this->environment.'/**/*'.self::CONFIG_EXTS, 'glob'); 41 | $loader->load($confDir.'/{services}'.self::CONFIG_EXTS, 'glob'); 42 | $loader->load($confDir.'/{services}_'.$this->environment.self::CONFIG_EXTS, 'glob'); 43 | } 44 | 45 | protected function configureRoutes(RouteCollectionBuilder $routes): void 46 | { 47 | $confDir = $this->getProjectDir().'/config'; 48 | 49 | $routes->import($confDir.'/{routes}/'.$this->environment.'/**/*'.self::CONFIG_EXTS, '/', 'glob'); 50 | $routes->import($confDir.'/{routes}/*'.self::CONFIG_EXTS, '/', 'glob'); 51 | $routes->import($confDir.'/{routes}'.self::CONFIG_EXTS, '/', 'glob'); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Migrations/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jolicode/symfony-jwt-article/3dc572628b0cb2fb9d949a54dbdbe79a64a027b9/src/Migrations/.gitignore -------------------------------------------------------------------------------- /src/Migrations/Version20190524150316.php: -------------------------------------------------------------------------------- 1 | addSql('CREATE SEQUENCE users_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); 15 | $this->addSql('CREATE TABLE users (id INT NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, api_token VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); 16 | $this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E9E7927C74 ON users (email)'); 17 | } 18 | 19 | public function down(Schema $schema): void 20 | { 21 | $this->addSql('DROP SEQUENCE users_id_seq CASCADE'); 22 | $this->addSql('DROP TABLE users'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Repository/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jolicode/symfony-jwt-article/3dc572628b0cb2fb9d949a54dbdbe79a64a027b9/src/Repository/.gitignore -------------------------------------------------------------------------------- /src/Repository/UserRepository.php: -------------------------------------------------------------------------------- 1 | userRepository = $userRepository; 21 | } 22 | 23 | public function supports(Request $request) 24 | { 25 | return true; 26 | } 27 | 28 | public function getCredentials(Request $request) 29 | { 30 | return [ 31 | 'apiToken' => $request->headers->get('PHP_AUTH_USER'), 32 | ]; 33 | } 34 | 35 | public function getUser($credentials, UserProviderInterface $userProvider) 36 | { 37 | return $this->userRepository->findOneBy([ 38 | 'apiToken' => $credentials['apiToken'], 39 | ]); 40 | } 41 | 42 | public function checkCredentials($credentials, UserInterface $user) 43 | { 44 | return true; 45 | } 46 | 47 | public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) 48 | { 49 | return null; 50 | } 51 | 52 | public function onAuthenticationFailure(Request $request, AuthenticationException $exception) 53 | { 54 | return new Response('', 401); 55 | } 56 | 57 | public function supportsRememberMe() 58 | { 59 | return false; 60 | } 61 | 62 | public function start(Request $request, AuthenticationException $authException = null) 63 | { 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /symfony.lock: -------------------------------------------------------------------------------- 1 | { 2 | "doctrine/annotations": { 3 | "version": "1.0", 4 | "recipe": { 5 | "repo": "github.com/symfony/recipes", 6 | "branch": "master", 7 | "version": "1.0", 8 | "ref": "cb4152ebcadbe620ea2261da1a1c5a9b8cea7672" 9 | }, 10 | "files": [ 11 | "config/routes/annotations.yaml" 12 | ] 13 | }, 14 | "doctrine/cache": { 15 | "version": "v1.8.0" 16 | }, 17 | "doctrine/collections": { 18 | "version": "v1.6.1" 19 | }, 20 | "doctrine/common": { 21 | "version": "v2.10.0" 22 | }, 23 | "doctrine/data-fixtures": { 24 | "version": "v1.3.1" 25 | }, 26 | "doctrine/dbal": { 27 | "version": "v2.9.2" 28 | }, 29 | "doctrine/doctrine-bundle": { 30 | "version": "1.6", 31 | "recipe": { 32 | "repo": "github.com/symfony/recipes", 33 | "branch": "master", 34 | "version": "1.6", 35 | "ref": "02bc9e7994b70f4fda004131a0c78b7b1bf09789" 36 | }, 37 | "files": [ 38 | "config/packages/doctrine.yaml", 39 | "config/packages/prod/doctrine.yaml", 40 | "src/Entity/.gitignore", 41 | "src/Repository/.gitignore" 42 | ] 43 | }, 44 | "doctrine/doctrine-cache-bundle": { 45 | "version": "1.3.5" 46 | }, 47 | "doctrine/doctrine-fixtures-bundle": { 48 | "version": "3.0", 49 | "recipe": { 50 | "repo": "github.com/symfony/recipes", 51 | "branch": "master", 52 | "version": "3.0", 53 | "ref": "fc52d86631a6dfd9fdf3381d0b7e3df2069e51b3" 54 | }, 55 | "files": [ 56 | "src/DataFixtures/AppFixtures.php" 57 | ] 58 | }, 59 | "doctrine/doctrine-migrations-bundle": { 60 | "version": "1.2", 61 | "recipe": { 62 | "repo": "github.com/symfony/recipes", 63 | "branch": "master", 64 | "version": "1.2", 65 | "ref": "c1431086fec31f17fbcfe6d6d7e92059458facc1" 66 | }, 67 | "files": [ 68 | "config/packages/doctrine_migrations.yaml", 69 | "src/Migrations/.gitignore" 70 | ] 71 | }, 72 | "doctrine/event-manager": { 73 | "version": "v1.0.0" 74 | }, 75 | "doctrine/inflector": { 76 | "version": "v1.3.0" 77 | }, 78 | "doctrine/instantiator": { 79 | "version": "1.2.0" 80 | }, 81 | "doctrine/lexer": { 82 | "version": "v1.0.1" 83 | }, 84 | "doctrine/migrations": { 85 | "version": "v2.0.2" 86 | }, 87 | "doctrine/orm": { 88 | "version": "v2.6.3" 89 | }, 90 | "doctrine/persistence": { 91 | "version": "1.1.1" 92 | }, 93 | "doctrine/reflection": { 94 | "version": "v1.0.0" 95 | }, 96 | "easycorp/easy-log-handler": { 97 | "version": "1.0", 98 | "recipe": { 99 | "repo": "github.com/symfony/recipes", 100 | "branch": "master", 101 | "version": "1.0", 102 | "ref": "70062abc2cd58794d2a90274502f81b55cd9951b" 103 | }, 104 | "files": [ 105 | "config/packages/dev/easy_log_handler.yaml" 106 | ] 107 | }, 108 | "egulias/email-validator": { 109 | "version": "2.1.7" 110 | }, 111 | "facebook/webdriver": { 112 | "version": "1.6.0" 113 | }, 114 | "fig/link-util": { 115 | "version": "1.0.0" 116 | }, 117 | "jdorn/sql-formatter": { 118 | "version": "v1.2.17" 119 | }, 120 | "monolog/monolog": { 121 | "version": "1.24.0" 122 | }, 123 | "nikic/php-parser": { 124 | "version": "v4.2.1" 125 | }, 126 | "ocramius/proxy-manager": { 127 | "version": "2.1.1" 128 | }, 129 | "phpdocumentor/reflection-common": { 130 | "version": "1.0.1" 131 | }, 132 | "phpdocumentor/reflection-docblock": { 133 | "version": "4.3.1" 134 | }, 135 | "phpdocumentor/type-resolver": { 136 | "version": "0.4.0" 137 | }, 138 | "psr/cache": { 139 | "version": "1.0.1" 140 | }, 141 | "psr/container": { 142 | "version": "1.0.0" 143 | }, 144 | "psr/link": { 145 | "version": "1.0.0" 146 | }, 147 | "psr/log": { 148 | "version": "1.1.0" 149 | }, 150 | "sensio/framework-extra-bundle": { 151 | "version": "5.2", 152 | "recipe": { 153 | "repo": "github.com/symfony/recipes", 154 | "branch": "master", 155 | "version": "5.2", 156 | "ref": "fb7e19da7f013d0d422fa9bce16f5c510e27609b" 157 | }, 158 | "files": [ 159 | "config/packages/sensio_framework_extra.yaml" 160 | ] 161 | }, 162 | "swiftmailer/swiftmailer": { 163 | "version": "v6.2.1" 164 | }, 165 | "symfony/asset": { 166 | "version": "v4.2.8" 167 | }, 168 | "symfony/browser-kit": { 169 | "version": "v4.2.8" 170 | }, 171 | "symfony/cache": { 172 | "version": "v4.2.8" 173 | }, 174 | "symfony/cache-contracts": { 175 | "version": "v1.1.1" 176 | }, 177 | "symfony/config": { 178 | "version": "v4.2.8" 179 | }, 180 | "symfony/console": { 181 | "version": "3.3", 182 | "recipe": { 183 | "repo": "github.com/symfony/recipes", 184 | "branch": "master", 185 | "version": "3.3", 186 | "ref": "482d233eb8de91ebd042992077bbd5838858890c" 187 | }, 188 | "files": [ 189 | "bin/console", 190 | "config/bootstrap.php" 191 | ] 192 | }, 193 | "symfony/contracts": { 194 | "version": "v1.1.0" 195 | }, 196 | "symfony/css-selector": { 197 | "version": "v4.2.8" 198 | }, 199 | "symfony/debug": { 200 | "version": "v4.2.8" 201 | }, 202 | "symfony/debug-bundle": { 203 | "version": "4.1", 204 | "recipe": { 205 | "repo": "github.com/symfony/recipes", 206 | "branch": "master", 207 | "version": "4.1", 208 | "ref": "f8863cbad2f2e58c4b65fa1eac892ab189971bea" 209 | }, 210 | "files": [ 211 | "config/packages/dev/debug.yaml" 212 | ] 213 | }, 214 | "symfony/debug-pack": { 215 | "version": "v1.0.7" 216 | }, 217 | "symfony/dependency-injection": { 218 | "version": "v4.2.8" 219 | }, 220 | "symfony/doctrine-bridge": { 221 | "version": "v4.2.8" 222 | }, 223 | "symfony/dom-crawler": { 224 | "version": "v4.2.8" 225 | }, 226 | "symfony/dotenv": { 227 | "version": "v4.2.8" 228 | }, 229 | "symfony/event-dispatcher": { 230 | "version": "v4.2.8" 231 | }, 232 | "symfony/event-dispatcher-contracts": { 233 | "version": "v1.1.1" 234 | }, 235 | "symfony/expression-language": { 236 | "version": "v4.2.8" 237 | }, 238 | "symfony/filesystem": { 239 | "version": "v4.2.8" 240 | }, 241 | "symfony/finder": { 242 | "version": "v4.2.8" 243 | }, 244 | "symfony/flex": { 245 | "version": "1.0", 246 | "recipe": { 247 | "repo": "github.com/symfony/recipes", 248 | "branch": "master", 249 | "version": "1.0", 250 | "ref": "dc3fc2e0334a4137c47cfd5a3ececc601fa61a0b" 251 | }, 252 | "files": [ 253 | ".env" 254 | ] 255 | }, 256 | "symfony/form": { 257 | "version": "v4.2.8" 258 | }, 259 | "symfony/framework-bundle": { 260 | "version": "4.2", 261 | "recipe": { 262 | "repo": "github.com/symfony/recipes", 263 | "branch": "master", 264 | "version": "4.2", 265 | "ref": "f64037a414de7d861f68e9b5b5c0e4f7425e2002" 266 | }, 267 | "files": [ 268 | "config/bootstrap.php", 269 | "config/packages/cache.yaml", 270 | "config/packages/framework.yaml", 271 | "config/packages/test/framework.yaml", 272 | "config/services.yaml", 273 | "public/index.php", 274 | "src/Controller/.gitignore", 275 | "src/Kernel.php" 276 | ] 277 | }, 278 | "symfony/http-client": { 279 | "version": "v4.3.0" 280 | }, 281 | "symfony/http-client-contracts": { 282 | "version": "v1.1.3" 283 | }, 284 | "symfony/http-foundation": { 285 | "version": "v4.2.8" 286 | }, 287 | "symfony/http-kernel": { 288 | "version": "v4.2.8" 289 | }, 290 | "symfony/inflector": { 291 | "version": "v4.2.8" 292 | }, 293 | "symfony/intl": { 294 | "version": "v4.2.8" 295 | }, 296 | "symfony/maker-bundle": { 297 | "version": "1.0", 298 | "recipe": { 299 | "repo": "github.com/symfony/recipes", 300 | "branch": "master", 301 | "version": "1.0", 302 | "ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f" 303 | } 304 | }, 305 | "symfony/mime": { 306 | "version": "v4.3.0" 307 | }, 308 | "symfony/monolog-bridge": { 309 | "version": "v4.2.8" 310 | }, 311 | "symfony/monolog-bundle": { 312 | "version": "3.1", 313 | "recipe": { 314 | "repo": "github.com/symfony/recipes", 315 | "branch": "master", 316 | "version": "3.1", 317 | "ref": "18ebf5a940573a20de06f9c4060101eeb438cf3d" 318 | }, 319 | "files": [ 320 | "config/packages/dev/monolog.yaml", 321 | "config/packages/prod/monolog.yaml", 322 | "config/packages/test/monolog.yaml" 323 | ] 324 | }, 325 | "symfony/options-resolver": { 326 | "version": "v4.2.8" 327 | }, 328 | "symfony/orm-pack": { 329 | "version": "v1.0.6" 330 | }, 331 | "symfony/panther": { 332 | "version": "v0.3.0" 333 | }, 334 | "symfony/phpunit-bridge": { 335 | "version": "4.1", 336 | "recipe": { 337 | "repo": "github.com/symfony/recipes", 338 | "branch": "master", 339 | "version": "4.1", 340 | "ref": "9dbf01cf67d791746ec772fe274c4d91cbf3c9ec" 341 | }, 342 | "files": [ 343 | ".env.test", 344 | "bin/phpunit", 345 | "config/bootstrap.php", 346 | "phpunit.xml.dist", 347 | "tests/.gitignore" 348 | ] 349 | }, 350 | "symfony/polyfill-intl-icu": { 351 | "version": "v1.11.0" 352 | }, 353 | "symfony/polyfill-intl-idn": { 354 | "version": "v1.11.0" 355 | }, 356 | "symfony/polyfill-mbstring": { 357 | "version": "v1.11.0" 358 | }, 359 | "symfony/polyfill-php72": { 360 | "version": "v1.11.0" 361 | }, 362 | "symfony/polyfill-php73": { 363 | "version": "v1.11.0" 364 | }, 365 | "symfony/process": { 366 | "version": "v4.2.8" 367 | }, 368 | "symfony/profiler-pack": { 369 | "version": "v1.0.4" 370 | }, 371 | "symfony/property-access": { 372 | "version": "v4.2.8" 373 | }, 374 | "symfony/property-info": { 375 | "version": "v4.2.8" 376 | }, 377 | "symfony/routing": { 378 | "version": "4.2", 379 | "recipe": { 380 | "repo": "github.com/symfony/recipes", 381 | "branch": "master", 382 | "version": "4.2", 383 | "ref": "5374e24d508ba8fd6ba9eb15170255fdb778316a" 384 | }, 385 | "files": [ 386 | "config/packages/dev/routing.yaml", 387 | "config/packages/routing.yaml", 388 | "config/packages/test/routing.yaml", 389 | "config/routes.yaml" 390 | ] 391 | }, 392 | "symfony/security-bundle": { 393 | "version": "3.3", 394 | "recipe": { 395 | "repo": "github.com/symfony/recipes", 396 | "branch": "master", 397 | "version": "3.3", 398 | "ref": "9a2034eca6d83d9cda632014e06995b8d9d9fd09" 399 | }, 400 | "files": [ 401 | "config/packages/security.yaml" 402 | ] 403 | }, 404 | "symfony/security-core": { 405 | "version": "v4.2.8" 406 | }, 407 | "symfony/security-csrf": { 408 | "version": "v4.2.8" 409 | }, 410 | "symfony/security-guard": { 411 | "version": "v4.2.8" 412 | }, 413 | "symfony/security-http": { 414 | "version": "v4.2.8" 415 | }, 416 | "symfony/serializer": { 417 | "version": "v4.2.8" 418 | }, 419 | "symfony/serializer-pack": { 420 | "version": "v1.0.2" 421 | }, 422 | "symfony/service-contracts": { 423 | "version": "v1.1.2" 424 | }, 425 | "symfony/stopwatch": { 426 | "version": "v4.2.8" 427 | }, 428 | "symfony/swiftmailer-bundle": { 429 | "version": "2.5", 430 | "recipe": { 431 | "repo": "github.com/symfony/recipes", 432 | "branch": "master", 433 | "version": "2.5", 434 | "ref": "3db029c03e452b4a23f7fc45cec7c922c2247eb8" 435 | }, 436 | "files": [ 437 | "config/packages/dev/swiftmailer.yaml", 438 | "config/packages/swiftmailer.yaml", 439 | "config/packages/test/swiftmailer.yaml" 440 | ] 441 | }, 442 | "symfony/test-pack": { 443 | "version": "v1.0.5" 444 | }, 445 | "symfony/translation": { 446 | "version": "3.3", 447 | "recipe": { 448 | "repo": "github.com/symfony/recipes", 449 | "branch": "master", 450 | "version": "3.3", 451 | "ref": "1fb02a6e1c8f3d4232cce485c9afa868d63b115a" 452 | }, 453 | "files": [ 454 | "config/packages/translation.yaml", 455 | "translations/.gitignore" 456 | ] 457 | }, 458 | "symfony/translation-contracts": { 459 | "version": "v1.1.2" 460 | }, 461 | "symfony/twig-bridge": { 462 | "version": "v4.2.8" 463 | }, 464 | "symfony/twig-bundle": { 465 | "version": "3.3", 466 | "recipe": { 467 | "repo": "github.com/symfony/recipes", 468 | "branch": "master", 469 | "version": "3.3", 470 | "ref": "369b5b29dc52b2c190002825ae7ec24ab6f962dd" 471 | }, 472 | "files": [ 473 | "config/packages/twig.yaml", 474 | "config/routes/dev/twig.yaml", 475 | "templates/base.html.twig" 476 | ] 477 | }, 478 | "symfony/validator": { 479 | "version": "4.1", 480 | "recipe": { 481 | "repo": "github.com/symfony/recipes", 482 | "branch": "master", 483 | "version": "4.1", 484 | "ref": "9a285e4ff3915c7cd086e9945f30591a926baf83" 485 | }, 486 | "files": [ 487 | "config/packages/test/validator.yaml", 488 | "config/packages/validator.yaml" 489 | ] 490 | }, 491 | "symfony/var-dumper": { 492 | "version": "v4.2.8" 493 | }, 494 | "symfony/var-exporter": { 495 | "version": "v4.2.8" 496 | }, 497 | "symfony/web-link": { 498 | "version": "v4.2.8" 499 | }, 500 | "symfony/web-profiler-bundle": { 501 | "version": "3.3", 502 | "recipe": { 503 | "repo": "github.com/symfony/recipes", 504 | "branch": "master", 505 | "version": "3.3", 506 | "ref": "6bdfa1a95f6b2e677ab985cd1af2eae35d62e0f6" 507 | }, 508 | "files": [ 509 | "config/packages/dev/web_profiler.yaml", 510 | "config/packages/test/web_profiler.yaml", 511 | "config/routes/dev/web_profiler.yaml" 512 | ] 513 | }, 514 | "symfony/web-server-bundle": { 515 | "version": "3.3", 516 | "recipe": { 517 | "repo": "github.com/symfony/recipes", 518 | "branch": "master", 519 | "version": "3.3", 520 | "ref": "dae9b39fd6717970be7601101ce5aa960bf53d9a" 521 | } 522 | }, 523 | "symfony/yaml": { 524 | "version": "v4.2.8" 525 | }, 526 | "twig/twig": { 527 | "version": "v2.10.0" 528 | }, 529 | "webmozart/assert": { 530 | "version": "1.4.0" 531 | }, 532 | "zendframework/zend-code": { 533 | "version": "3.3.1" 534 | }, 535 | "zendframework/zend-eventmanager": { 536 | "version": "3.2.1" 537 | } 538 | } 539 | -------------------------------------------------------------------------------- /templates/base.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% block title %}Welcome!{% endblock %} 6 | {% block stylesheets %} 7 | 8 | {% endblock %} 9 | 10 | 11 |
12 | {% block body %}{% endblock %} 13 |
14 | {% block javascripts %}{% endblock %} 15 | 16 | 17 | -------------------------------------------------------------------------------- /templates/frontend/index.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'base.html.twig' %} 2 | 3 | {% block title %}Hello{% endblock %} 4 | 5 | {% block body %} 6 |

You don't need JWT

7 | 8 |
9 |
10 |
11 |
12 |
Are you connected?
13 |

14 | Yes 15 | 18 | No 19 |

20 |
21 |
22 |
23 |
24 |
25 |
26 |
Sign In
27 |

28 |

29 | 32 |
33 | 34 | 35 | 36 | 37 |
38 | 41 |
42 |

43 |
44 |
45 |
46 |
47 | 48 |
49 |
50 |
51 |
52 |
Call the API
53 | 56 |

57 | Status Code: N/A 58 |

59 |
60 |
61 |
62 |
63 | {% endblock %} 64 | 65 | {% block javascripts %} 66 | {{ parent() }} 67 | 141 | 142 | {% endblock %} 143 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jolicode/symfony-jwt-article/3dc572628b0cb2fb9d949a54dbdbe79a64a027b9/tests/.gitignore -------------------------------------------------------------------------------- /translations/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jolicode/symfony-jwt-article/3dc572628b0cb2fb9d949a54dbdbe79a64a027b9/translations/.gitignore --------------------------------------------------------------------------------