I am looking for a simple solution to protect my routes with the Basic Authentication mechanism with Cro. In my example I'd like to see a 401 Unauthorized
if you don't provide any credentials at all. If you provide wrong credentials I like to see a 403 Forbidden
In my code example I never saw the MyBasicAuth
middleware being called:
class MyUser does Cro::HTTP::Auth {
has $.username;
}
subset LoggedInUser of MyUser where { .username.defined }
class MyBasicAuth does Cro::HTTP::Auth::Basic[MyUser, "username"] {
method authenticate(Str $user, Str $pass --> Bool) {
# No, don't actually do this!
say "authentication called";
my $success = $user eq 'admin' && $pass eq 'secret';
forbidden without $success;
return $success
}
}
sub routes() is export {
my %storage;
route {
before MyBasicAuth.new;
post -> LoggedInUser $user, 'api' {
request-body -> %json-object {
my $uuid = UUID.new(:version(4));
%storage{$uuid} = %json-object;
created "api/$uuid", 'application/json', %json-object;
}
}
}
}
This structure:
route {
before MyBasicAuth.new;
post -> LoggedInUser $user, 'api' {
...
}
}
Depends on the new before
/after
semantics in the upcoming Cro 0.8.0. In the current Cro release at the time of asking/writing - and those prior to it - before
in a route
block would apply only to routes that had already been matched. However, this was too late for middleware that was meant to impact what would match. The way to do this prior to Cro 0.8.0 is to either mount the middleware at server level, or to do something like this:
route {
before MyBasicAuth.new;
delegate <*> => route {
post -> LoggedInUser $user, 'api' {
...
}
}
}
Which ensures that the middleware is applied before any route matching is considered. This isn't so pretty, thus the changes in the upcoming 0.8.0 (which also will introduce a before-matched
that has the original before
semantics).
Finally, forbidden without $success;
is not going to work here. The forbidden
sub is part of Cro::HTTP::Router
and for use in route handlers, whereas middleware is not tied to the router (so you could decide to route requests in a different way, for example, without losing the ability to use all of the middleware). The contract of the authenticate
method is that it returns a truthy value determining what should happen; it's not an appropriate place to try and force a different response code.
A failure to match an auth constraint like LoggedInUser
will produce a 401. To rewrite that, add an after
in the outermost route
block to map it:
route {
before MyBasicAuth.new;
after { forbidden if response.status == 401; }
delegate <*> => route {
post -> LoggedInUser $user, 'api' {
...
}
}
}