Alternative OPAC logins RFC

From Koha Wiki
Jump to navigation Jump to search

Alternative OPAC logins

Status: unknown
Sponsored by:
Developed by:
Expected for:
Bug number: Bug
Work in progress repository:
Description: Could it be possible to let patrons login to the OPAC with other credentials than the Username/password provided by Koha itself? OAuth and OpenID look like obvious candidates - what are their pros and cons, and are there other/better alternatives?


This is more of an attempt at getting a little brainstorming going, rather than the description of any specific plans for a new development.

The problem: Patrons probably have too many usernames/passwords already, resulting in them forgetting them and/or re-using credentials from other sites, resulting in reduced security. Could we let users use credentials from outside Koha to log in?

Creating a website that uses OAuth/OpenID/etc for logging in is probably not rocket science. An added challenge for Koha will be connecting external credentials to an actual/physical borrower.

Technical alternatives

OAuth

Oauth2 (Google)

A basic oauth2 login for google is currently being worked on by Perth Bible College. If the email address associated with the google account is registered to a user in Koha, then that user is automatically logged in.

Developed by Nicholas van Oudtshoorn // Perth Bible College

This is a work in progress - though it is used in production on the pbc opac. See also Bug 10988.


Instructions for setting up OAuth2 (Google) using a patch file

These instructions were written for Koha 3.14.03; but should be easily translatable to other versions. But your mileage might vary! These instructions are based on the patches in [1].

Step 1: Patch your installation

Change to your koha directory. This is usually

/usr/share/koha

Download the installedinstance patch file from bug 10988:

wget "http://bugs.koha-community.org/bugzilla3/attachment.cgi?id=25570" -O googleoauth2_installedinstance.patch

Then run it:

patch -p1 < googleoauth2_installedinstance.patch
Step 2: Update your Koha database

Run the following SQL to update the Koha database. The easiest way to do this is by using phpMyAdmin, and selecting the Koha Database, then clicking on the SQL tab. Paste in and execute the following:

INSERT INTO `systempreferences` (`variable`, `value`, `options`, `explanation`, `type`) VALUES ('GoogleOAuth2', '0', NULL, 'if ON, allows the use of Google OAuth2 for login', 'YesNo'), ('GoogleOAuth2ClientID', '', NULL, 'Client ID for the web app registered with google', 'Free'), ('GoogleOAuth2ClientSecret', '', NULL, 'Client Secret for the web app registered with google', 'Free'), ('GoogleOAuth2Domain', '', NULL, 'Restrict OAuth2 to this domain. Leave blank for all google domains', 'Free');

Alternatively, you can run the

mysql -uKOHAUSER -pKOHAPASSWORD

command.
Then type

USE KOHADATABASENAME

then paste the following:

INSERT INTO `systempreferences` (`variable`, `value`, `options`, `explanation`, `type`) VALUES ('GoogleOAuth2', '0', NULL, 'if ON, allows the use of Google OAuth2 for login', 'YesNo'), ('GoogleOAuth2ClientID', '', NULL, 'Client ID for the web app registered with google', 'Free'), ('GoogleOAuth2ClientSecret', '', NULL, 'Client Secret for the web app registered with google', 'Free'), ('GoogleOAuth2Domain', '', NULL, 'Restrict OAuth2 to this domain. Leave blank for all google domains', 'Free');

Type

exit

.

Step 3: Restart your web server

As you normally would!

Step 4: Set up OAuth2 with Google

Create a web app in the google cloud console:

Go to https://cloud.google.com/console

Create a project, and give it some details

Open the Project by clicking on it

Under APIs & auth menu, open "Registered Apps" and click "Register App"

Give it a name and make sure you select "Web app", click ok

Under OAuth 2.0 Client ID:

          under web origin, type <your_opac_address> in the redirect uri

          enter <your_opac_address>/cgi-bin/koha/svc/googleoauth2

click Generate


Step 5: Adjust Koha OAuth2 Preferences

Set the GoogleOAuth2ClientID and GoogleOAuth2ClientSecret according to the values generated in step 4. If you want to restrict OAuth2 logins to a specifc domain, you can enter it into the GoogleOAuth2Domain preference.

Step 6: Enjoy!

If you can't use the patch file, follow these instructions instead of step 1

Patch C4/Auth.pm

Edit the file lib/C4/Auth.pm Change line 37 from

use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap $cas $caslogout);

to

use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $googleoauth2 $ldap $cas $caslogout); 

at about line 54, replace

%EXPORT_TAGS = ( EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)] );
$ldap = C4::Context->config('useldapserver') || 0;
$cas = C4::Context->preference('casAuthentication');

with

%EXPORT_TAGS = ( EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)] );
$googleoauth2= C4::Context->preference('GoogleOAuth2');
$ldap = C4::Context->config('useldapserver') || 0;
$cas = C4::Context->preference('casAuthentication');


At about line 396, replace

             BranchesLoop              => GetBranchesLoop($opac_name),
             BranchCategoriesLoop      => GetBranchCategories( 'searchdomain', 1, $opac_name ),
             CalendarFirstDayOfWeek    => (C4::Context->preference("CalendarFirstDayOfWeek") eq "Sunday")?0:1,
             LibraryName               => "" . C4::Context->preference("LibraryName"),
             LibraryNameTitle          => "" . $LibraryNameTitle,

with

             BranchesLoop              => GetBranchesLoop($opac_name),
             BranchCategoriesLoop      => GetBranchCategories( 'searchdomain', 1, $opac_name ),
             CalendarFirstDayOfWeek    => (C4::Context->preference("CalendarFirstDayOfWeek") eq "Sunday")?0:1,
             GoogleOAuth2              => (C4::Context->preference("GoogleOAuth2")),
             LibraryName               => "" . C4::Context->preference("LibraryName"),
             LibraryNameTitle          => "" . $LibraryNameTitle,


At about line 1106, just before the code that begins:

     my $self_url = $query->url( -absolute => 1 );
     $template->param(
         url         => $self_url,

insert the following

    if ($googleoauth2) {
        if ($query->param("OAuth2Failed")) {
            my $reason = $query->param('OAuth2Failed');
            $template->param(invalidOAuth2Login => $reason);
        }
    }
Update the Koha database to include the new system preferences

Run the following SQL to update the Koha database. You can do this either by using the

mysql -uKOHAUSER -pKOHAPASSWORD

command, or else from within something like phpMyAdmin.

INSERT INTO `systempreferences` (`variable`, `value`, `options`, `explanation`, `type`) VALUES ('GoogleOAuth2', '0', NULL, 'if ON, allows the use of Google OAuth2 for login', 'YesNo'), ('GoogleOAuth2ClientID', '', NULL, 'Client ID for the web app registered with google', 'Free'), ('GoogleOAuth2ClientSecret', '', NULL, 'Client Secret for the web app registered with google', 'Free'), ('GoogleOAuth2Domain', '', NULL, 'Restrict OAuth2 to this domain. Leave blank for all google domains', 'Free');
Tell Koha about the new system preferences related to OAuth2

Edit the file

prog/en/modules/admin/preferences/admin.pref

At the end of the file, copy the following:

    Google OAuth2:
        -
            - pref: GoogleOAuth2
              choices:
                yes: Use
                no: "Don't Use"
            - Google OAuth2 login.
            - You will need to select OAuth2 when creating an app in the google cloud console, and set the web origin to your_opac_url and the redirect url to your_opac_url/cgi-bin/koha/svc/oauthlogin .
        -
            - Google OAuth2 Client ID
            - pref: GoogleOAuth2ClientID
        -
            - Google OAuth2 Client Secret
            - pref: GoogleOAuth2ClientSecret
        -
            - Google OAuth2 Restrict to domain
            - pref: GoogleOAuth2Domain
Install the actual code to do OAuth2 (Google) authentication

Create a new file called

opac-tmpl/svc/googleoauth2

with the following content:

#!/bin/perl -w
# Copyright vanoudt@gmail.com 2014
# Based on persona code from chris@bigballofwax.co.nz 2013
# 
# This file is part of Koha.
# 
# Koha is free software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# 
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along
# with Koha; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
#
# Basic OAuth2 authentication for google goes like this
# First: get your clientid, clientsecret from google. At this stage, tell google that
# your redirect url is /cgi-bin/koha/svc/oauthlogin
#
# The first thing that happens when this script is called is that one gets redirected
# to an authentication url from google
#
# If successful, that then redirects back to this script, setting a CODE parameter
# which we use to look up a json authentication token. This token includes an encrypted 
# json id_token, which we round-trip back to google to decrypt. Finally, we can extract
# the email address from this.
#
# There is some room for improvement here.
# In particular, Google recommends verifying and decrypting the id_token locally, which means caching
# some information and updating it daily. But that would make things a lot faster

use strict;
use warnings;
use CGI qw/escape/;
use C4::Auth;
use C4::Context;
use C4::Output;

use LWP::UserAgent; 
use HTTP::Request::Common qw{ POST };
use JSON;

my $scope="openid email";
my $host = C4::Context->preference('OPACBaseURL');
my $restricttodomain = C4::Context->preference('GoogleOAuth2Domain');
my $redirecturl = 'http://' . $host . '/cgi-bin/koha/svc/googleoauth2';
my $issuer = 'accounts.google.com';
my $clientid = C4::Context->preference('GoogleOAuth2ClientID');
my $clientsecret = C4::Context->preference('GoogleOAuth2ClientSecret');

my $query = new CGI;

sub loginfailed {
  my $query = shift;
  my $reason = shift;
  $query->delete('code');
  $query->param('OAuth2Failed'=>$reason);
  my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
    {
        template_name   => 'opac-user.tmpl',
        query           => $query,
        type            => 'opac',
        authnotrequired => 0,
        flagsrequired   => { borrow => 1 },
    }
  );
  $template->param( 'invalidOAuth2Login' => $reason );
  $template->param( 'loginprompt' => 1);
  output_html_with_http_headers $query, $cookie, $template->output;
}
#die $query->param('code');
if (defined $query->param('error')) {
       loginfailed($query, 'An authentication error occurred. (Error:' . $query->param('error') . ')');
} elsif (defined $query->param('code')) {
       my $code=$query->param('code');
       my $ua = LWP::UserAgent->new();
       my $request = POST('https://accounts.google.com/o/oauth2/token', [
                        code => $code,
                        client_id => $clientid,
                        client_secret => $clientsecret,
                       redirect_uri => $redirecturl,
                               grant_type => 'authorization_code',
                        $scope => $scope
    ]);
       my $response =  $ua->request($request)->decoded_content ;
       my $json = decode_json ( $response);
       if ( exists($json->{'id_token'}) ) {
               $request = POST('https://www.googleapis.com/oauth2/v1/tokeninfo', [
                       id_token => $json->{'id_token'}
               ]);
               $response = $ua->request($request)->decoded_content;
               $json = decode_json ( $response);
               # Confirm (as google suggests) that the issuer and audience are what we expect them to be
               if (($json->{'issuer'} eq $issuer) && ($json->{'audience'} eq $clientid) && exists($json->{'email'})) {
                       my $email = $json->{'email'};
                       my ( $userid, $cookie, $sessionID ) =
                            checkauth( $query, 1,  { borrow => 1 }, 'opac', $email );
                       if ($userid) { # A valid user has logged in
                               print $query->redirect( -uri => '/cgi-bin/koha/opac-user.pl', -cookie => $cookie );
                       } else {
                               loginfailed($query, 'The email address you are trying to use is not associated with a borrower in this library.');
                       }
               } else { # something went wrong with getting appropriate credentials
                       loginfailed($query, 'Failed to get proper credentials from google.');
               }
       } else { # Failed to get ID Token
               loginfailed($query, 'An authentication error occurred. (Error: No ID Token was supplied)');
       }       

} else {
    my  $prompt = '';
    $prompt = $query->param('reauthenticate') unless not (defined $query->param('reauthenticate'));

    my $authorisationurl =  'https://accounts.google.com/o/oauth2/auth?' .
                            'response_type=code&' .
                            'redirect_uri=' . escape ( $redirecturl ) . '&' .
                            'client_id=' . escape ( $clientid ) . '&' .
                            'scope=' . escape ( $scope ) . '&';
   if ( $restricttodomain ne '' ) {
      $authorisationurl .= 'hd=' . $restricttodomain . '&';
   }
    $authorisationurl .=    'prompt=' . escape ( $query->param('reauthenticate') );
       print $query->redirect($authorisationurl);
}
Change your templates to log in using OAuth2 (Google)

The following instructions are for the Bootstrap theme. If you are using prog or ccsr, simply change all references to

/cgi-bin/koha/opac-user.pl

with

/cgi-bin/koha/svc/googleoauth2

Edit the file

includes/masthead.inc

, from about line 58, changing

                                                 <li role="presentation"><a href="/cgi-bin/koha/opac-shelves.pl?display=privateshelves" tabindex="-1" role="menuitem" class="listmenulink">New list</a></li>
                                             [% END %]
                                         [% ELSE %]
                                            <li role="presentation"><a href="/cgi-bin/koha/opac-user.pl" tabindex="-1" class="menu-inactive loginModal-trigger" role="menuitem">Log in to create your own lists</a></li>
                                         [% END # / IF loggedinusername %]
                                     [% END # / IF opacuserlogin %]

to

                                                 <li role="presentation"><a href="/cgi-bin/koha/opac-shelves.pl?display=privateshelves" tabindex="-1" role="menuitem" class="listmenulink">New list</a></li>
                                             [% END %]
                                         [% ELSE %]
                                           [% IF ( GoogleOAuth2 ) %]
                                               <li role="presentation"><a href="/cgi-bin/koha/svc/googleoauth2" tabindex="-1" class="menu-inactive" role="menuitem">Log in to create your own lists</a></li>
                                            [% ELSE %]
                                               <li role="presentation"><a href="/cgi-bin/koha/opac-user.pl" tabindex="-1" class="menu-inactive loginModal-trigger" role="menuitem">Log in to create your own lists</a></li>
                                           [% END %]

                                             <li role="presentation"><a href="/cgi-bin/koha/opac-user.pl" tabindex="-1" class="menu-inactive loginModal-trigger" role="menuitem">Log in to create your own lists</a></li>
                                         [% END # / IF loggedinusername %]
                                     [% END # / IF opacuserlogin %]

Edit the file

includes/masthead.inc

, from about line 71, changing

                         <div id="members">
                             <ul class="nav pull-right">
                                 [% UNLESS ( loggedinusername ) %]
                                    <li><a href="/cgi-bin/koha/opac-user.pl" class="loginModal-trigger" role="button" data-toggle="modal">Log in to your account</a></li>
                                 [% END %]
                                 [% IF ( loggedinusername ) %]
                                     <li><p class="members navbar-text">Welcome, <a href="/cgi-bin/koha/opac-user.pl"><span class="loggedinusername">[% FOREACH USER_INF IN USER_INFO %][% USER_INF.title %] [% USER_INF.firstname %] [% USER_INF.surname %][% END %]</span></a></p></li>

to

                         <div id="members">
                             <ul class="nav pull-right">
                                 [% UNLESS ( loggedinusername ) %]
                                   [% IF ( GoogleOAuth2 ) %]
                                       <li><a href="/cgi-bin/koha/svc/googleoauth2" role="button" data-toggle="modal">Log in to your account</a></li>
                                   [% ELSE %]
                                          <li><a href="/cgi-bin/koha/opac-user.pl"  class="loginModal-trigger" role="button" data-toggle="modal">Log in to your account</a></li>
                                   [% END %]
                                 [% END %]
                                 [% IF ( loggedinusername ) %]
                                     <li><p class="members navbar-text">Welcome, <a href="/cgi-bin/koha/opac-user.pl"><span class="loggedinusername">[% FOREACH USER_INF IN USER_INFO %][% USER_INF.title %] [% USER_INF.firstname %] [% USER_INF.surname %][% END %]</span></a></p></li>

Edit the file

modules/opac-auth.tt

, from about line 84, changing

 
                             [% END # / IF casAuthentication %]
 
                             <form action="[% url %]" name="auth" id="auth" method="post">
                                 <input type="hidden" name="koha_login_context" value="opac" />
                                 <fieldset class="brief">

to

 
                             [% END # / IF casAuthentication %]
 
                           [% IF ( invalidOAuth2Login ) %]
                               <h4>Automatic Login</h4>
                               <p>Sorry, your automatic login failed. <span class="error">[% invalidOAuth2Login %]</span></p>
                               <p>Please note that automatic login will only work if you are using the email address registered with this library.</p>
                               <p>If you want to, you can try to <a href="/cgi-bin/koha/svc/googleoauth2?reauthenticate=select_account">login using a differen account</a>
                               <h4>Local login</h4>
                               <p>If you can't login automatically, you can still login in manually: </p>
                           [% END %]

                             <form action="[% url %]" name="auth" id="auth" method="post">
                                 <input type="hidden" name="koha_login_context" value="opac" />
                                 <fieldset class="brief">

Useful Userscripts for OAuth2 (Google) on public terminals

When using Google OAuth2 in a public computer, there are some nice javascripts that can be injected to make this a whole lot more secure. Basically, the following userscripts disable the google account chooser, and log a person out of their google accounts when they log out of Koha. This is not very useful or polite outside of a public terminal, hence my thinking to do it with userscripts.

Just putting it here as a reference for anyone thinking to use this.

--------------------------------------------------------------------------------
// ==UserScript==
// @id             accounts.google.com-forcenocookie@scriptish
// @name           Disable persistent google login
// @version        1.0
// @namespace      
// @author         
// @description    
// @include        https://accounts.google.com/*
// @run-at         document-end
// ==/UserScript==
document.getElementsByName("PersistentCookie")[0].value='no';
--------------------------------------------------------------------------------
// ==UserScript==
// @id             koha-full-oauth2-logout@scriptish
// @name           Force Full Logout
// @version        1.0
// @namespace
// @author
// @description
// @include        http://OPACURLHERE/*
// @run-at         document-end
// ==/UserScript==
document.getElementById('logoutmenu').href="https://www.google.com/accounts/Logout?continue=https://appengine.google.com/_ah/logout?continue="+document.getElementById('logoutmenu').href;
document.getElementById('logout').href="https://www.google.com/accounts/Logout?continue=https://appengine.google.com/_ah/logout?continue="+document.getElementById('logoutmenu').href;

OpenID

WedID

Connecting external credentials to an actual/physical borrower

How do we know that a user logging in with /OpenID/OAuth/etc should be given access to any given account in Koha? Do they have to turn up at the library and tell us their OpenID? Are there other ways?

Inspiration