Question

I'm using FOSRestBundle for my REST API and so far it has been a great tool. I use HTTP Basic Auth and in most of the cases it works just fine. However, I have problems with the bundle's exception behaviour when bad credentials are submitted. When handling exceptions (via the integrated authentication handlers or the exception mapping configuration), the bundle always gives me a response with the correct HTTP status and JSON/XML content similar to this:

{
   "code": 401,
   "message": "You are not authenticated"
}

This is fine, it also works when no authentication information is submitted at all. However, when submitting bad credentials (e.g. unknown username or incorrect password) I get the HTTP code 401 Bad credentials (which is fine) with an empty message body. Instead, I would have expected something similar to the JSON above.

Is it a bug or a configuration issue on my side? I would also love to know how these kinds of authentication errors are exactly handled by the bundle, since overriding the BadCredentialsException's status code in the codes section of the bundle's exception configuration section seems to be ignored.

Thanks!

Was it helpful?

Solution

Alright, after digging into the bundle's code some more, I figured it out. The problem results from the way bad credentials are handled by Symfony's HTTP Basic Authentication impementation. The 401 Bad Credentials response is a custom response created by BasicAuthenticationEntryPoint, which is called by the BasicAuthenticationListener's handle function, immediately after an AuthenticationException has been thrown in the same function. So there is no way of catching this exception with a listener:

public function handle(GetResponseEvent $event)
{
    $request = $event->getRequest();

    if (false === $username = $request->headers->get('PHP_AUTH_USER', false)) {
        return;
    }

    if (null !== $token = $this->securityContext->getToken()) {
        if ($token instanceof UsernamePasswordToken && $token->isAuthenticated() && $token->getUsername() === $username) {
            return;
        }
    }

    if (null !== $this->logger) {
        $this->logger->info(sprintf('Basic Authentication Authorization header found for user "%s"', $username));
    }

    try {
        $token = $this->authenticationManager->authenticate(new UsernamePasswordToken($username, $request->headers->get('PHP_AUTH_PW'), $this->providerKey));
        $this->securityContext->setToken($token);
    } catch (AuthenticationException $failed) {
        $this->securityContext->setToken(null);

        if (null !== $this->logger) {
            $this->logger->info(sprintf('Authentication request failed for user "%s": %s', $username, $failed->getMessage()));
        }

        if ($this->ignoreFailure) {
            return;
        }

        $event->setResponse($this->authenticationEntryPoint->start($request, $failed));
    }
}

The entry point's start function creates the custom response, with no exceptions involved:

public function start(Request $request, AuthenticationException $authException = null)
{
    $response = new Response();
    $response->headers->set('WWW-Authenticate', sprintf('Basic realm="%s"', $this->realmName));
    $response->setStatusCode(401, $authException ? $authException->getMessage() : null);

    return $response;
}

The fist if-clause in the handle function above also explains why it works in the case of "no user credentials at all", since in that case, the listener just stops trying to authenticate the user, and therefore an exception will be thrown by Symfony's firewall listeners (not quite sure where exactly), so FOSRestBundle's AccessDeniedListener is able to catch the AuthenticationException and do its thing.

OTHER TIPS

You can extend AccessDeniedListener and tell FOSRestBundle to use your own listener with the parameter %fos_rest.access_denied_listener.class%. (service definition)

parameters:
    fos_rest.access_denied_listener.class: Your\Namespace\For\AccessDeniedListener

Then add an additional check for BadCredentialsException and emmit an HttpException with the desired code/message similar to the check for AuthenticationException at Line 70.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top