Symfony JSON Login
Context
Most Web application use a "form login", which is rendered with Twig. This is fine when the application is entirely server-side rendered. But when the application is a Single Page Application (SPA), I mean when the backend is a REST API, the form login is not suitable. I need then a JSON login mechanism.
Looking at the Symfony documentation, I found the JSON Login mechanism. But this documentation is not complete and needs more steps to be implemented.
JSON Login with in memory Users
Here, I am going to start from a bare Symfony application and implement the JSON login mechanism, and the users are not going to be stored in a database but in memory. More precisely, I am going to store users in the "config/packages/security.yaml" file.
Step 1: Create a new Symfony application
symfony new symfony-json-login cd symfony-json-login
Step 2: Install the security component
composer require symfony/security-bundle lexik/jwt-authentication-bundle
Step 3: Modify the "config/packages/security.yaml" file
The "security.yaml" file should look like this:
security:
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
providers:
users_in_memory:
memory:
users:
admin: { password: '$2y$13$OnHfV10QdWDhM2wU.t1YrOZwilLd4NL372cENaNQ9ctHltOP0G2.S' , roles: ['ROLE_ADMIN'] }
user: { password: '$2y$13$OnHfV10QdWDhM2wU.t1YrOZwilLd4NL372cENaNQ9ctHltOP0G2.S', roles: ['ROLE_USER'] }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
api:
pattern: ^/api
stateless: true
json_login:
check_path: /api/login
username_path: username
password_path: password
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
jwt: ~
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/profile, roles: ROLE_USER }
We can see that we have two users: "admin" and "user". The password for both users is the same. But we will see later how to hash the password. I precise that we have to wipe out the initial content of the "security.yaml" file and replace it with the above content.
Step 4: Create a route for the login
In "config/routes/api.yaml", we have to create a route for the login:
api_login: path: /api/login methods: [POST]
Step 5: Ajust the token settings
In "config/packages/lexik_jwt_authentication.yaml", we have to adjust the token settings:
lexik_jwt_authentication: secret_key: '%env(resolve:JWT_SECRET_KEY)%' public_key: '%env(resolve:JWT_PUBLIC_KEY)%' token_ttl: '%env(JWT_TOKEN_TTL)%'
Step 6: Set the ".env" file
In the preceding step, we used the "env()" function to get the values from the ".env" file. So we have to set the ".env" file:
###> lexik/jwt-authentication-bundle ### JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem ###< lexik/jwt-authentication-bundle ### JWT_TOKEN_TTL=3600
Step 7: Generate the JWT keys
We have to generate the JWT keys:
mkdir config/jwt; cd config/jwt openssl genrsa -out private.pem 4096 openssl rsa -pubout -in private.pem -out public.pem
Step 8: Hash the password
We have to hash the password. We can use the following code to hash the password:
php bin/console security:hash-passwordPaste the hashed password in the "security.yaml" file.
Step 9: Test the application
We can test the application by sending a POST request to the "/api/login" route with the following JSON content:
{ "username": "admin", "password": "admin" }You will get a token in the response.
To use that token, you have to send it in the "Authorization" header with the "Bearer" prefix. Dont forget the space between the "Bearer" and the token.