OAuth2 login

Hello,

I’m currently trying to replace old OpenID and googleclient authentication in my web server application by the oauth2 module from SWISH. So far I took the files oauth2.pl, login.pl and auth_google.pl removing dependencies to Swish and Google basically by:

  • replacing any ‘swish*’ by ‘server*’
  • replacing atoms ‘google’ by a Server variable
  • moving server_attributes to a separate config file to be consulted and setting up Google, Yahoo and Facebook

(https://github.com/revuloj/voko-cetonio/tree/master/pro/auth)

First tests look promising beside the fact, that only Google permits the callback URL to use http instead of https. So I will need further tests behind a https proxy.

I raise this topic here, asking if it would make sense to add this stuff somehow to the standard libraries of SWI-Prolog deprecating the outdated OpenID lib there? Otherwise projects will fork and hack this code making bug fixing and security patches an unsafe matter.

What do you think?

Kind regards,
Wolfram.

This is surely a mess. The stuff in swish was setup to eventually evolve into a new library. In the meanwhile we have the pack identity for which I’m not entirely sure how the things relate.

Bottom line is that this is a hard topic. There are so many options wrt. identification and user management that it is really hard to create something that deals with even a fair subset of the scenarios.

Hello Jan,

I had a look onto the identity package. I think it is complementary doing user profile management. I do this already using a sqlite database.

Meanwhile I got almost through the authentication process with Google using the new oauth2 lib. Still the redirection to the main page at the end does not work:

I tried with http_status_reply:

auth_config:reply_logged_in(Options) :-
  % get user data and then...
  http_status_reply(moved_temporary('../red/'),current_output,[],_)

getting a HTTP-500 instead saying:
Internal server error: Illegal HTTP parameter: HTTP/1.1 302 Moved Temporary

Not sure, what I miss here. With the old googleclient lib I could simply return using:

http_redirect(moved_temporary, '../red/', Claim.client_data)

As the Claim in oauth2 is not storing the original request data, I suppose I have to save it in the session before starting the login process and get it back from there afterwards.

I’m just wondering if I miss a more straightforward way to do the redirect at the end.

Kind regards,
Wolfram.

Well, http_status_reply is internal and should not be used directly. http_redirect is the proper way to redirect. Why would that not work? Part of the problems could be that in SWISH, the login is handled in separate window as we want to avoid reloading the swish page and loose work. I guess some stuff needs to change to get a more conventional flow where the login happens in the same browser window though a series of redirects.

And yes, I guess if you want to trigger login from a request that requires this and after login you want to continue the request you should store the information somewhere (session, browser …)

I’m afraid it is a bit too long ago that I wrote this stuff for having the entire flow still in mind :frowning: I hope you can manage …

Hi,

Login and logout with Google works now. I thought I had to give a full Request structure to http_redirect but the implementation showed to me, that it is used only for path calculations. So with a handler id I don’t need it:
http_redirect(moved_temporary, location_by_id(landing), _)

I will work on FB and Yahoo login during next days, but I suppose it is only a matter of using https and tweaking the setup.

Thanks for your hints,
Wolfram.

1 Like

Please share if you figure it out. In my experience oauth2 login differs a little between each provider :frowning:

Hi Jan,

yes you are right: every ID provider is special. My adjusted code is here:


If you like I could send part of it as diff compared to the original SWISH files, but anyway I resume the main points here:

(1) Yahoo and FB don’t provide the users email address in the default setup/scope. FB offers an app certification process, may be you could get also email addresses in this case.
It is not an issue in my special case, as my only 200 users ask manually for registration and I use the ID providers only to recognize them and avoid storing their passwords in my application.
If you need verified email addresses you would have to implement an email verification process in your application or check how you could get a wider scope from the ID provider.

(2) FB seems to implement OAuth2 standard but not OpenID Connect. This means you don’t have an endpoint discovery and don’t get an id_token field within the access-token. Still there is an endpoint returning a name and an id of the user as JSON declared as text/javascript. So I just added some more fallback code to handle this special case.

In oauth2.pl:

read_reply2(200, media(text/javascript, _Attributes), In, Dict) :- !,
	json_read_dict(In, Dict).

call_login(Request, ServerID, TokenInfo) :-
	oauth2_user_info(ServerID, TokenInfo, UserInfo),
	login(Request, ServerID, TokenInfo, UserInfo),
	!.

In server_auth.pl (your auth_google.pl) adding login/4:

oauth2:login(_Request, Server, TokenInfo, UserInfo) :-
    debug(oauth, '(4) UserInfo: ~p', [UserInfo]),
    http_open_session(_SessionID, []),
    TokenInfo1 = TokenInfo.put(_{ user_info:UserInfo }),
    http_session_assert(oauth2(Server,TokenInfo1)),
    reply_logged_in([ identity_provider(Server),
                    %%  email(UserInfo.email), %%%!!!
                      user_info(UserInfo)
                    ]).

Side note about naming: I should have used “Provider” everywhere I used “Server” to be more clear. - just a hint if you intend to adopt some of my changes.

You could checkout the history on Github for more details:

There is still at least one small issue left with the login/3, login/4:

If you trigger the login process again without explicitly logging out you end up with several acess_tokens in your session: either from different providers or same provider, but different timestamp/expirations. It still works but may led to unexpected side effects.

I would suggest either to kill the full session here:

http_open_session(_SessionID, [renew(true)])

or retract at least all the oauth2(_, _) from it before asserting the new one. It may depend, what is saved else into the session which one is the preferred option here. In my case I tend to renew the full session.

The rest of FB special handling I added in my application specific login/registration code:

user_info(Provider,UserInfo) :- 
	(var(Provider) ; \+ (Provider = facebook)), !,
	auth_config:user_info(_, Provider, UserInfo).

user_info(facebook,UserInfo) :- !,
	http_in_session(_SessionID),
	http_session_data(oauth2(facebook, TokenInfo)),
	UserInfo = TokenInfo.get(user_info).

user_info_sub(facebook, UserInfo, UserInfo.id) :- !.
user_info_sub(_, UserInfo, UserInfo.sub).

Kind regards,
Wolfram.

1 Like