I'm having trouble setting up a wsse security on my Symfony API. I'm following the tutorial Configure WSSE on Symfony with FOSRestBundle based on How to Create a custom Authentication Provider
The issues are :
How do I manage to use FOSUserBundle with WSSE (especially salt) ? I don't want the front to retrieve the salt of each user that sends a request.
I want to make the registration route anonymously reachable and each time I try to HTTP Request without sending the x-wsse i'm getting an error :
"message":"A Token was not found in the SecurityContext.", "class":"Symfony\\Component\\Security\\Core\\Exception\\AuthenticationCredentialsNotFoundException"
app/config/security.yml
security:
encoders:
FOS\UserBundle\Model\UserInterface: sha512
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
firewalls:
wsse_secured:
pattern: ^/
stateless: true
wsse: true
anonymous : false
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_provider: form.csrf_provider
logout: true
anonymous: true
access_control:
# I tried using - { path: ^/users/$, roles: IS_AUTHENTICATED_ANONYMOUSLY, methods: [POST] }
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/, role: ROLE_ADMIN }
WsseProvider
class WsseProvider implements AuthenticationProviderInterface
{
private $userProvider;
private $cacheDir;
public function __construct(UserProviderInterface $userProvider, $cacheDir)
{
$this->userProvider = $userProvider;
$this->cacheDir = $cacheDir;
}
public function authenticate(TokenInterface $token){
$user = $this->userProvider->loadUserByUsername($token->getUsername());
if(!$user){
throw new AuthenticationException("Bad credentials... Did you forgot your username ?");
}
if ($user && $this->validateDigest($token->digest, $token->nonce, $token->created, $user->getPassword())) {
$authenticatedToken = new WsseUserToken($user->getRoles());
$authenticatedToken->setUser($user);
return $authenticatedToken;
}
}
protected function validateDigest($digest, $nonce, $created, $secret){
// Check created time is not in the future
if (strtotime($created) > time()) {
throw new AuthenticationException("Back to the future...");
}
// Expire timestamp after 5 minutes
if (time() - strtotime($created) > 300) {
throw new AuthenticationException("Too late for this timestamp... Watch your watch.");
}
// Validate nonce is unique within 5 minutes
if (file_exists($this->cacheDir.'/'.$nonce) && file_get_contents($this->cacheDir.'/'.$nonce) + 300 > time()) {
throw new NonceExpiredException('Previously used nonce detected');
}
// If cache directory does not exist we create it
if (!is_dir($this->cacheDir)) {
mkdir($this->cacheDir, 0777, true);
}
file_put_contents($this->cacheDir.'/'.$nonce, time());
// Validate Secret
$expected = base64_encode(sha1(base64_decode($nonce).$created.$secret, true));
if($digest !== $expected){
throw new AuthenticationException("Bad credentials ! Digest is not as expected.");
}
return true;
}
public function supports(TokenInterface $token){
return $token instanceof WsseUserToken;
}
}
WsseUserToken
class WsseUserToken extends AbstractToken
{
public $created;
public $digest;
public $nonce;
public function __construct(array $roles = array())
{
parent::__construct($roles);
$this->setAuthenticated(count($roles) > 0);
}
public function getCredentials()
{
return '';
}
}
WsseListener
class WsseListener implements ListenerInterface
{
protected $securityContext;
protected $authenticationManager;
protected $logger;
public function __construct(SecurityContextInterface $securityContext,
AuthenticationManagerInterface $authenticationManager
)
{
$this->securityContext = $securityContext;
$this->authenticationManager = $authenticationManager;
}
public function handle(GetResponseEvent $event)
{
$request = $event->getRequest();
$wsseRegex = '/UsernameToken Username="([^"]+)", PasswordDigest="([^"]+)", Nonce="([^"]+)", Created="([^"]+)"/';
if (!$request->headers->has('x-wsse') || 1 !== preg_match($wsseRegex, $request->headers->get('x-wsse'), $matches) ) {
return;
}
$token = new WsseUserToken();
$token->setUser($matches[1]);
$token->digest = $matches[2];
$token->nonce = $matches[3];
$token->created = $matches[4];
try {
$authToken = $this->authenticationManager->authenticate($token);
$this->securityContext->setToken($authToken);
return;
} catch (AuthenticationException $failed) {
$failedMessage = 'WSSE Login failed for '.$token->getUsername().'. Why ? '.$failed->getMessage();
// Deny authentication with a '403 Forbidden' HTTP response
$response = new Response();
$response->setStatusCode(403);
$response->setContent($failedMessage);
$event->setResponse($response);
return;
}
// By default deny authorization
$response = new Response();
$response->setStatusCode(403);
$event->setResponse($response);
}
}
Any help greatly appreciated this is so killing me...