Custom http authenticator failing after upgrading to v8.0.3


I’m trying to create a custom http:authenticate/3 method and it’s doing weird stuff in SWI-Prolog version 8.0.3.

My code works as expected in version 7.6.4 but after upgrading, no cigar!

My code:

http:authenticate(my_auth_type, _Request, [user(anonymous)]).

:- http_handler(root(.), home, [authentication(my_auth_type)]).

home(Request) :-
    memberchk(user(User), Request),

The http:authenticate method is being called BUT the user is not being passed to the handler in the request. I traced the problem to following code in the http_dispatch library code:

auth_expansion(Request0, Request, Options) :-
    authentication(Options, Request0, Extra),
    append(Extra, Request, Request0).

(This is new mechanism in the upgraded version by the looks)
It looks like Request0 is the original request, Request is the modified request, but the append does not resolve obviously because the user is not in the Request0 already.

My question is, am I missing something obvious here or is this bug?


I guess the best is to look in the provided HTTP basic and digest implementations.
authentication/3 is supposed to determine the user based on what is provided in the request. Note that these hooks can only be used to deal with authentication based on the browser itself and where the authentication is passed along with the request. You need something else if you want the common cookie and login form based authentication. Anne Ogborn wrote an identity package. SWISH also provides a quite comprehensive authentication mechanism.

If something broke your code that cannot work with the current implementation and there is a good way to make the work again, please submit a pull request.

Thanks, I will look at it again next week. For now I have found a work around by creating a custom router that does the auth and then passes the request on to http_dispatch. It works essentially that same and can be retro-fitted to the http_dispatch options later.

I have the same problem when upgrading from 7.6.x to 8.0.3. I have the impression that the
append args should be in reversed order here:

append(Extra, Request0, Request)


With this workatound it works, so the thrick is exchanging Request and Request0 in the append:

:- http_request_expansion(my_auth_expansion, 110).

my_auth_expansion(Request0, Request, Options) :-
	debug(auth,'>> my_auth_exp ~q ~q',[Request0,Options]),
    authentication(Options, Request0, Extra),
	append(Extra, Request0, Request),
	debug(auth,'<< my_auth_exp ~q',[Request]).

authentication([], _, []).
authentication([authentication(Type)|Options], Request, Fields) :-
    !, debug(auth,'authentication ~q',[Fields]),
    (   http:authenticate(Type, Request, XFields)
	->  append(XFields, More, Fields),
	debug(auth,'authentication 2 ~q',[Fields]),
        authentication(Options, Request, More)
    ;   memberchk(path(Path), Request),
        permission_error(access, http_location, Path)
authentication([_|Options], Request, Fields) :-
    authentication(Options, Request, Fields).


there is still a flaw in my previous workaround, as the original auth_expansion still invokes the authentication handler even if this always fails because of the small bug in the append arg order.

May be it’s best to wait until Jan will find time to fix it.

Alternatively one could use an alternative request option [my_auth(my_auth_handler)] instead of [authentication(my_auth_handler)] or call the workaround handler with lower rank(90) and add an additional check to avoid passing through the authentication handler twice.

After some more fiddling this works now:

/*** workaround bug in SWI 8.0.3 - exchanged Request / Request0 in append ***/
:- abolish(http_dispatch:request_expansion/2).
:- http_request_expansion(my_auth_expansion, 100).
my_auth_expansion(Request0, Request, Options) :-
	debug(auth,'>> my_auth_exp ~q ~q',[Request0,Options]),
    http_dispatch:authentication(Options, Request0, Extra),
	append(Extra, Request0, Request),
	debug(auth,'<< my_auth_exp ~q',[Request]).

That is a pretty cool workaround, I just learnt how abolish works!

I actually solved the problem by creating a new dispacher which handles authentication and then calls http_dispatch when the authentication has been verified (or redirects if not!).

It looks a bit like the following:

server(Port) :- http_server(authenticated_route, [port(Port)]).

authenticated_route(Request) :-
	  memberchk(path(P), Request),
	  once(handle_route(P, Request)).
% the path is excluded, continue	  
handle_route(Path, Request) :-

% check that the authenticated flag is set, if it is there, the user is logged in
% this flag is set on the session during the login process
handle_route(_, Request) :-

% by default, throw back to login page.
handle_route(_, Request) :-
	http_redirect(see_other, location_by_id(login_page), Request).	

It’s a bit rough, but good enough for my purposes.