We have a FOSUserBundle login system authenticating via LDAP and the fr3d LDAP bundle. It behaves like a normal multiple page application using sessions. We also have several RESTful endpoints using the FOSRestbundle and normal sessions for authentication. However, we need to share a few end points with an external application.
We managed to implement JWT using the Lexik bundle. It returns a token just fine. However, I don't know the best way to let a user using our login form to get this token so their request can pass it along in the header or session. My question is how to allow a user to login to our application in a stateful manner, but also receive the JWT and pass it to the server on ajax requests. This way I can allow external clients to connect directly to the API. Below is my symfony2 security configuration, security.yml:
security:
#erase_credentials: false
encoders:
FOS\UserBundle\Model\UserInterface: bcrypt
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
chain_provider:
chain:
providers: [my_user_provider, fr3d_ldapbundle]
in_memory:
memory:
users:
admin: { password: secret, roles: 'ROLE_ADMIN' }
my_user_provider:
id: app.custom_user_provider
fos_userbundle:
id: fos_user.user_provider.username
fr3d_ldapbundle:
id: fr3d_ldap.security.user.provider
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, role: IS_AUTHENTICATED_FULLY }
- { path: ^/api, role: IS_AUTHENTICATED_FULLY }
- { path: ^/api/login, role: IS_AUTHENTICATED_ANONYMOUSLY }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
api_login:
pattern: ^/api/login
fr3d_ldap: ~
provider: chain_provider
anonymous: true
stateless: false
form_login:
check_path: /api/login_check
username_parameter: username
password_parameter: password
require_previous_session: false
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
api:
pattern: ^/api
provider: chain_provider
stateless: false
lexik_jwt:
throw_exceptions: true
create_entry_point: true
main:
pattern: ^/
fr3d_ldap: ~
form_login:
# provider: fos_userbundle
provider: chain_provider
always_use_default_target_path: true
default_target_path: /
csrf_provider: security.csrf.token_manager
logout: true
anonymous: true
switch_user: { role: ROLE_LIMS-BIOINFO}
EDIT:
Based on Kévin's answer I decided to implement a custom Twig extension to get the token for the logged in user on each page load:
AppBundle/Extension/JsonWebToken.php:
<?php
namespace AppBundle\Extension;
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class JsonWebToken extends \Twig_Extension
{
/**
* @var ContainerInterface
*/
private $container;
/**
* @var JWTManagerInterface
*/
private $jwt;
public function __construct(ContainerInterface $container, JWTManagerInterface $jwt)
{
$this->container = $container;
$this->jwt = $jwt;
}
public function getName()
{
return 'json_web_token';
}
public function getFunctions()
{
return [
'json_web_token' => new \Twig_Function_Method($this, 'getToken')
];
}
public function getToken()
{
$user = $this->container->get('security.token_storage')->getToken()->getUser();
$token = $this->jwt->create($user);
return $token;
}
}
app/config/services.yml:
app.twig_jwt:
class: AppBundle\Extension\JsonWebToken
arguments: ["@service_container", "@lexik_jwt_authentication.jwt_manager"]
tags:
- { name: twig.extension }
app/Resources/views/layout.html.twig
<script>window.jsonWebToken = '{{ json_web_token() }}';</script>
app/Resources/modules/layout/app.js:
var jsonWebToken = window.jsonWebToken;
$.ajaxSetup({
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization","Bearer " + jsonWebToken);
}
});
So far this seems to be working well. It let's my external API users and internal application users share the same authentication methods.
As the JWT token must be stored client-side (and not in a cookie to prevent CSRF attacks), you can use the create
method of the lexik_jwt_authentication.jwt_manager
service provided by LexikJWTAuthenticationBundle to generate a token after the login, then inject this token in a <script>
tag in the generated HTML.