tuupola/slim-basic-auth

Custom Authentication Method - Passing Error Messages

Opened this issue · 11 comments

How would you pass back a custom error message from a custom authenticator? Obviously my example below is pulled completely out of thin air and would never work, but I think you get what I mean? Somehow, there must be a way to pass a message from here, back to the error handler, or alter the $arguments?

class DbAuthenticator implements AuthenticatorInterface {
    private $settings;
 
    public function __construct($settings) {
        $this->settings = $settings;
    }
 
    public function __invoke(array $arguments) {
        if (wrongPassword($user, $pass)) {
            $returnAmessageSomeHow = "Invalid password";
            $failedLogins = $failedLogins + 1;
            return false;
        } 
        if (invalidUsername($user, $pass)) {
            $returnAmessageSomeHow = "Invalid username";
            return false;
        }
    }

}

then...

$container["HttpBasicAuthentication"] = function ($container) {
    return new HttpBasicAuthentication([
        "path" => [ ... ],
        "authenticator" => new DbAuthenticator($container['settings']),
        "error" => function ($request, $response, $arguments) {
            $data = [];
            $data["status"] = "error";
            $data["message"] = $messageReturnedFromAuthenticatorSomehow;
            return $response->write(json_encode($data, JSON_UNESCAPED_SLASHES));
        }
    ]);
};

This is not currently possible. It is however a good idea and I should implement it in the near future.

I am not sure if it's implemented already, but if that's not the case is there another way I can pass container settings to my custom authenticator?

This bug report is about custom error messages. Container you can already pass with something like:

$app->add(new \Slim\Middleware\HttpBasicAuthentication([
    "path" => "/admin",
    "realm" => "Protected",
    "authenticator" => new CustomAuthenticator($container)
]));

Hi, has this issue (custom error messagens) been developed?

It is still in TODO list but plan is to implement this feature since it is quite useful.

Hi,

Any news on this feature for 3.0 ?
Mika, do you have some implementation ideas in mind ?

I wonder if I could implement this feature by passing a Response object to the authenticator, and return a Response instead of a boolean.
We could check the status of the response to determine the success/error state, and we give ability to the Authenticator to manage it's own status?

What do you think ?

Thank you.

Adding this just before the final return in the process function (src/HttpBasicAuthentication.php) worked for me, may not the best way to handle it though.

           if(method_exists($this->options['authenticator'], 'getCustomErrorMessage')) {
                return $this->processError($response, [
                        "message" => $this->options['authenticator']->getCustomErrorMessage()
                ]);
            }

Thanks @apuleus , but I dont understood how this works. What does getCustomErrorMessage do?

getCustomErrorMessage() returns a string with the customer error message that my authenticator set. I placed it right before the code that returns the $response and default message. You can see that original code snippet below. It changes the behavior by checking if the "getCustomErrorMessage()" method exists in my custom authenticator.

Original Code

 /* Check if user authenticates. */
        if (false === $this->options["authenticator"]($params)) {
            /* Set response headers before giving it to error callback */
            $response = (new ResponseFactory)
                ->createResponse(401)
                ->withHeader(
                    "WWW-Authenticate",
                    sprintf('Basic realm="%s"', $this->options["realm"])
                );
            return $this->processError($response, [
                "message" => "Authentication failed"
            ]);
        }

Code with my addition

 /* Check if user authenticates. */
        if (false === $this->options["authenticator"]($params)) {
            /* Set response headers before giving it to error callback */
            $response = (new ResponseFactory)
                ->createResponse(401)
                ->withHeader(
                    "WWW-Authenticate",
                    sprintf('Basic realm="%s"', $this->options["realm"])
                );
            if(method_exists($this->options['authenticator'], 'getCustomErrorMessage')) {
                return $this->processError($response, [
                        "message" => $this->options['authenticator']->getCustomErrorMessage()
                ]);
            }
            return $this->processError($response, [
                "message" => "Authentication failed"
            ]);
        }

The function in my authenticator class

        
public function getCustomErrorMessage() {
  return (string) $this->errorMessage;
}
        

Like I said, may not be the best way, but it worked for my purposes. The reason why I needed the authenticator to handle the error message is due to the authenticator class having the ability to lock people out and this was an easy way to allow the authenticator to change the default message when that happens.

Thanks @apuleus!!!