I'm trying to create REST API with JWT authentification in Symfony5. At first I've tried to do as written here https://h-benkachoud.medium.com/symfony-rest-api-without-fosrestbundle-using-jwt-authentication-part-2-be394d0924dd . Difficulties appeared when I've tried to make method getTokenUser:
/**
* #param JWTTokenManagerInterface $JWTManager
* #return JsonResponse
* #Route("/api/login_check", name="api_login_check", methods={"POST"})
*/
public function getTokenUser(UserInterface $user,JWTTokenManagerInterface $JWTManager)
{
return new JsonResponse(['token' => $JWTManager->create($user)]);
}
Symfony says that UserInterface is not service so it can't Autowire it.
Ok, then I've tried to find another articles about this problem. But surprisingly they just doesn't say how to write this method. For example, here https://digitalfortress.tech/php/jwt-authentication-with-symfony/ appears like route /api/login_check must work authomatically if it configured in security.yaml and in routes.yaml. But no, it doesn't work.
So how must I write controller?
My security.yaml is:
security:
encoders:
App\Entity\User:
algorithm: bcrypt
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
login:
pattern: ^/api/login
stateless: true
anonymous: true
json_login:
check_path: /api/login_check
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
api:
pattern: ^/api
stateless: true
provider: app_user_provider
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#firewalls-authentication
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
- { path: ^/register, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
it's true you do not need this method. You should refer to the documentation of the bundle you are using:
https://github.com/lexik/LexikJWTAuthenticationBundle/blob/2.x/Resources/doc/index.rst
In fact the login URL is handled by the security component of Symfony and rely on the configuration you set under json_login key :) .
Once again, it's detailed in the documentation (section "Usage").
Related
I'm trying to allow only to register (POST method) a new user (route: /api/users), I tried to follow the documentation (https://symfony.com/doc/current/security/firewall_restriction.html#restricting-by-http-methods), but when I test with Postman, I still manage to see all users with the GET method.
The security.yaml file :
security:
# https://symfony.com/doc/current/security/authenticator_manager.html
enable_authenticator_manager: true
# https://symfony.com/doc/current/security.html#c-hashing-passwords
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
App\Entity\User:
algorithm: auto
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
registration:
pattern: ^/api/users
stateless: true
methods: [POST]
login:
pattern: ^/api/login
stateless: true
json_login:
check_path: /api/login_check
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
api:
pattern: ^/api
stateless: true
jwt: ~
main:
lazy: true
provider: app_user_provider
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#firewalls-authentication
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
# access_control:
# - { path: ^/api/login, roles: PUBLIC_ACCESS }
# - { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
Short version:
firewalls:
registration:
pattern: ^/api/users
stateless: true
methods: [POST]
What I should see when trying to access /api/users with the GET method is a code 401, "JWT Token not found".
But I don't, I see the users and their datas.
I had to configure it in the access control at the end:
access_control:
- { path: ^/api/users, roles: IS_AUTHENTICATED_FULLY, methods: [GET, PUT, DELETE] }
I want to create an admin user to handle back-end and to use jwt token to protect my API routes.
In my security.yaml file i add a main firewall to handle login form authentication :
main:
anonymous: lazy
provider: app_user_provider
guard:
authenticators:
- App\Security\LoginFormAuthenticator
logout:
path: app_logout
and i config access control :
- { path: '^/admin', roles: ROLE_ADMIN }
- { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
Now, if i use login form, i can sign in as an admin :
After sign in i can access to the admin controller.
I added jwt lexik bundle and i added config to security.yaml :
login:
pattern: ^/api/login
stateless: true
anonymous: true
json_login:
check_path: /api/login_check
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
api:
pattern: ^/api
stateless: true
anonymous: true
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
And i added config to api_platform.yaml:
api_platform:
mapping:
paths: ['%kernel.project_dir%/src/Entity']
patch_formats:
json: ['application/merge-patch+json']
swagger:
versions: [3]
api_keys:
apiKey:
name: Authorization
type: header
title: API partage de mandats
version: 1.0.0
collection:
pagination:
client_enabled: true
enabled_parameter_name: pagination # optional
eager_loading:
enabled: false
I want to make public access to my api, and prevent testing api without token :
My api look like this :
The problem is : i can got ressource event if i'm not authenticated :
My current acces control is :
How can i add config to security.yaml to prevent public access to api ressources ?
Could, you just add this line to your access_control:
security:
# ...
access_control:
- { path: '^/api/mandates', roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: '^/api', roles: ROLE_USER }
I fix the problem.
All i need to do is to add a slash to my route in the firewall :
api:
pattern: ^/api/
stateless: true
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
Now everything work like i want .
Before explain why what I need your help, just keep in mind that I am totally new on Symfony 3.4, so please be lenient with me.
What I try to create is a RESTful API.
For the admin section, I'd like my users to log in every day in order to access the application dashboard. I already do that, by using the LexikJWTAuthenticationBundle with a token that expires every 24 hours.
Then, my API will have several client applications that should access my data using an access token, but not using JWT. Those client applications will be mobile apps ( implemented on React Native ) and desktop apps ( implemented on Electron).
The way I thought I should authorize those applications to access my API is by providing them with an Authentication token using the oAuth protocol.
So, the basic question is, if I could config both authorization methods ( JWT + oAuth ) at the same time for the same resources.
So, for example, let's say I have the following endpoint: /api/v1/repository/chocolates.
With the jms_serializer I am able to choose what data I want to display on each group, so for a user has the ROLE_ADMIN I can display all the product information, while for the user has the ROLE_SOME_GRANT will only be possible to see the title and the description.
So is it possible to access the /api/v1/repository/chocolates either by using the JWT token my admins have or by using the oAuth token my client apps will have?
For the moment my app/config/security.yml has the following setup:
# To get started with security, check out the documentation:
# https://symfony.com/doc/current/security.html
security:
encoders:
FOS\UserBundle\Model\UserInterface: bcrypt
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_NUTRITIONIST: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
# https://symfony.com/doc/current/security.html#b-configuring-how-users-are-loaded
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
oauth_token:
pattern: ^/oauth/v2/token
security: false
oauth_authorize:
pattern: ^/oauth/v2/auth
security: false
api_user_management:
pattern: ^/api/v1/user/(login|request\/reset|password\/reset)
anonymous: true
stateless: true
api_user_management_safe:
pattern: ^/api/v1/user/(login|request\/reset|password\/reset)
anonymous: true
stateless: true
api_user_login:
pattern: ^/api/v1/login
stateless: true
anonymous: true
form_login:
check_path: /api/v1/login
require_previous_session: false
username_parameter: username
password_parameter: password
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
logout: true
#############################################
## HERE IF I CHANGE THE fos_oauth to false
## I CAN ACCESS MY ENDPOINT : /api/v1/register/new/user
## WHILE IF THAT IS TRUE I GET THE ERROR MESSAGE:
##
## {
## "error": "invalid_grant",
## "error_description": "The access token provided is invalid."
## }
#############################################
api_access:
pattern: ^/api/v1
stateless: true
#lexik_jwt: ~
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
fos_oauth: true
access_control:
- { path: ^/api/v1/user/request/reset, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api/v1/user/password/reset, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api/v1/user/update, role: IS_AUTHENTICATED_FULLY }
- { path: ^/api/v1/user/((?!request)|(?!password)|(?!update)), role: IS_AUTHENTICATED_FULLY }
- { path: ^/api/v1/register/new/user, role: IS_AUTHENTICATED_FULLY }
- { path: ^/api/v1/, role: IS_AUTHENTICATED_FULLY }
So, based on that security setup do you think there's a way to have multiple authorization services in my application?
I'd like to add the "Authorize" button on Swagger, like described here : https://api-platform.com/docs/core/jwt#documenting-the-authentication-mechanism-with-swaggeropen-api
I installed LexikJWTAuthenticationBundle, it works fine with Curl. But when I browse to http://localhost:8000/api, I only see {"code":401,"message":"JWT Token not found"}.
Am I missing something?
Here's my security.yaml:
security:
encoders:
App\Entity\User:
algorithm: bcrypt
providers:
db_provider:
entity:
class: App\Entity\User
property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
api_login:
pattern: ^/api/login
stateless: true
anonymous: true
form_login:
check_path: /api/login_check
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
require_previous_session: false
api:
pattern: ^/api
stateless: true
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
main:
pattern: ^/
anonymous: ~
provider: db_provider
form_login:
login_path: app_security_login
check_path: app_security_login
csrf_token_generator: security.csrf.token_manager
logout:
path: /logout
target: /
remember_me:
secret: '%kernel.secret%'
lifetime: 604800
path: /
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_USER }
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
access_decision_manager:
strategy: unanimous
And my api_platform.yaml:
api_platform:
title: 'My project'
version: '0.0.1'
mapping:
paths: ['%kernel.project_dir%/src/Entity']
swagger:
api_keys:
apiKey:
name: Authorization
type: header
Bit late to this, but I faced this same issue. Your security configuration is stating that any route beginning with /api requires authentication, which includes /api itself. If you want to keep the documentation on the /api route, add a trailing slash to the security configuration;
firewalls:
...
api:
pattern: ^/api/
and
access_control:
- { path: ^/api/, roles: IS_AUTHENTICATED_FULLY }
This will leave /api as publicly accessible, whilst requiring a valid token to be provided for /api/*.
Alternatively, you can leave the security configuration as it is and move the documentation to a different URL (e.g. /docs). For this, you may need to add /docs as an IS_AUTHENTICATED_ANONYMOUSLY path under access_control depending on your other rules.
Then when the documentation page is accessible, click the Authorize button at the top of the page and enter Bearer <valid JWT token>.
It's old but here is the solution in 2021
You have to decorate open api factory
Edit :
Don't put hyphen (-) in the name of the authorization and in the scheme key otherwise it won't work (probably other special characters)
In config/packages/api_platform.yaml :
swagger:
versions: [3]
api_keys:
JWT: // The name of the authorization to display on swagger UI
name: Authorization
type: header
In config/services.yaml :
App\OpenApi\JwtDecorator:
decorates: 'api_platform.openapi.factory'
arguments: [ '#App\OpenApi\JwtDecorator.inner' ]
autoconfigure: false
enter code here
The decorating service : in OpenApi\JwtDecorator
class JwtDecorator implements OpenApiFactoryInterface
{
public function __construct(
private OpenApiFactoryInterface $decorated
) {}
public function __invoke(array $context = []): \ApiPlatform\Core\OpenApi\OpenApi
{
$openApi = ($this->decorated)($context);
$schemas = $openApi->getComponents()->getSecuritySchemes();
$schemas['JWT'] = new \ArrayObject([
'type' => 'http',
'scheme' => 'bearer',
'bearerFormat' => 'JWT'
]);
return $openApi;
}
}
I could fix my problem. In my case problem was, in public and private openssl keys. First I fixed it, the second, problem was in encoding. I used the source below to achieve result. Just take a look to link below
LexikJWTAuthenticationBundleSandbox
I have created a REST API, i'm actually trying to secure it with the FOSOAuthServerBundle.
I created the route newclient to generate new client, now i would like to allow only the 'admin' to access to this url.
I guess it's possible to do this with scopes but i can't figure out how.
Here is my security.yml:
security:
providers:
user_provider:
id: user_provider
firewalls:
doc:
pattern: ^/doc
security: false
oauth_token:
pattern: ^/oauth/v2/token
security: false
oauth_authorize:
pattern: ^/oauth/v2/auth
provider: user_provider
anonymous: true
api:
pattern: ^/
fos_oauth: true
stateless: true
anonymous: false
access_control:
- { path: ^/, roles: ROLE_CLIENT }
- { path: ^/newclient, roles: ROLE_ADMIN }
My config.yml
fos_oauth_server:
db_driver: orm
client_class: WS\RESTBundle\Entity\Client
access_token_class: WS\RESTBundle\Entity\AccessToken
refresh_token_class: WS\RESTBundle\Entity\RefreshToken
auth_code_class: WS\RESTBundle\Entity\AuthCode
Any tips ?
Actually desling with scopes is not the best way to do it, plus the bundle does not support it : https://github.com/FriendsOfSymfony/FOSOAuthServerBundle/issues/231
I made use the User model, add a "role" method and check if the current user's role is enough to access the to the routes.
Here is the piece of code
//Get the current user, check if it's an admin
$token = $this->container->get('security.context')->getToken()->getToken();
$accessToken = $this->container
->get('fos_oauth_server.access_token_manager.default')
->findTokenBy(array('token' => $token));
$client = $accessToken->getClient();
if ($client->getRole() == 'admin') {
...
}
Not sure if it's the best way to do it, any advices welcome !