The required configuration in hydra.conf:
enable_google_login = 1 google_client_id = 238429sdjkds….apps.googleusercontent.com
and optionally persona_allowed_domains to restrict to one or more domains.
3QWDDLBR5DGFK5Y3TDMK55R2SCHRHFVO2KW2BMZGIYRTIQEZC45AC GS4SFHCPF76AX2U4NLLJGUISF6Y4AHYWEW3GLTDCKVDDXLSIGXUQC Z4NL5TWBVTPUTDYAE7YH7C5OPWLF5JKGFEDLYWD5OX4TISW67CUAC IFY7BYPSDD4FA7LYXENS7DUTGKOKZSS6U5C2LVC26UOJAX3GIMXQC HL6ZYWHFSOSTGCUEP2K554F5OY4RWAHJYS2MA2V3JMZIG262LFKAC 2DHE2ZAKR4AU7OE6E5CYNFWVGQXLHEX5LFKVU43PMBVX3QW6RHFAC XOOSZ3WSJVMXS2ZMAS64QYUGAIBOMU5754QDSRB2AYDKGVNNH2FAC 6K5PBUUN4GQAMOVX5BS6YYMHJ3PIF2PPZBTIEQ4R7BQNHC23GS3AC T4LLYESZ2HUXSLKZ6GNBLVWUVG7R5IDFHYHYO773QIZ6QTOOXR2AC J5UVLXOK6EDIL5I7VKWH4V2QDS4DPD7FHRK6XBWSXFRQS4JKXFZQC XJRJ4J7M6BC433TBLWHHKX7UYYCFX6M7ZQLUEYYTREPCSM6M3RDQC JFW656FT5JSDCA5NBNFGTJABIR2LR5VRDRYI7KF54PYNFDSL4DMAC LZVO64YG43JD7YMZSCTZNOBS5ROZA4FMPKJW2YOMHX2V5PTGBVWQC LSZLZHJYGXZTCNH4JUXU7W23MW5PBVM4OBMWRRVNEDROMIBUVQNAC 36ZTCZ4FDV6ILURQEIGFHCP57ALZH6OWYNN3MBXG2QXZSBULBPMAC 2CZSW5S53UW7ACDNT5T3UQNJKFBCXCBYLQ5CL6GVCCOMKBGXZIEQC QVIQAYZTTDRODPFP6HCWOETJ2OYLECU34FD4K5YKL7ENLG6HI3GAC D44B24QC6NCED6DVUYP2IJJEVBG2JNBKPBRRSLI5UXQTKA23DJQQC D5QIOJGPKQJIYBUCSC3MFJ3TXLPNZ2XMI37GXMFRVRFWWR2VMTFAC 4JPNFWRBFCFER74NDL3XZXNQ25YQCPD7E4XKYKF7VCWIESE56G4AC JARRBLZDQ2JZWY7IUVPTOT7WJMBPMLFLF2MGLVGOYROAAISYGLSAC PFB5ZUQWW67FRYY54QGTAVTWYMUKXLXRA2XCY3IWK6HZNE6DDLFAC OEPUOUNBNTHTFZVDXREGBQCKFRCWMVP2MDVK4OA47VK2DBKEWVYAC P75LFRF4GMC5KSIHC4XDBFRYIO36XUABQ2BN3EJXL42764BFAZUAC VVRM3EGCPCYA3GQEGZUEMOGEEYTMMUZK46UFU3R46AKZC62ZEMRQC WDKFN4B2M7BUF4S7X6YA5AKHOJKHLG65LWNQVOSIBTKFEZELBMKAC QL55ECJ6KMMBUOWQ6LKSOVN7L43CH4S6SPE2AQ3VX3KSGC32RP4AC DV43UILUJNNU4DJMQ5NIZ2TY5Y4NOPQZXXQQJNKINUKA2VBAJ2QAC MQMF2LBWWPW2SOZKC3O7P4TBJJ3V6RBVQ52OYA4KCGXS7G6SHWQAC SZYDW2DG5Z7BR3ICKWDXVUNSMCDSXMYZUB6FQ4W2B2FVZJD6PULQC my $assertion = $c->stash->{params}->{assertion} or die;my $ua = new LWP::UserAgent;my $response = $ua->post('https://verifier.login.persona.org/verify',{ assertion => $assertion,audience => $c->uri_for('/')});error($c, "Did not get a response from Persona.") unless $response->is_success;
# If persona_allowed_domains is set, check if the email address returned is on these domains.# When not configured, allow all domains.
# If persona_allowed_domains is set, check if the email address# returned is on these domains. When not configured, allow all# domains.
if (!$user) {
if ($user) {# Automatically upgrade Persona accounts to Google accounts.if ($user->type eq "persona" && $type eq "google") {$user->update({type => "google"});}die "You cannot login via login type '$type'.\n" if $user->type ne $type;} else {
my $ua = new LWP::UserAgent;my $response = $ua->post('https://verifier.login.persona.org/verify',{ assertion => $assertion,audience => $c->uri_for('/')});error($c, "Did not get a response from Persona.") unless $response->is_success;my $d = decode_json($response->decoded_content) or die;error($c, "Persona says: $d->{reason}") if $d->{status} ne "okay";doEmailLogin($self, $c, "persona", $d->{email}, undef);}# From https://www.googleapis.com/oauth2/v3/certs. Should probably not# hard-code this.my $googleKeys = <<'EOF';{"keys": [{"kty": "RSA","alg": "RS256","use": "sig","kid": "10685afd5291883ce668345afd77201390406f82","n": "xeNopuszp35W6H1w2Tw4OrSwT8BZ9f7-2PoOyWZmfMmUDmYT2uxrZezDK0YLap5LVmpLNcpZP5Hj67_32NU3my4qfA-SlxuJMUxHWJF7Dqr-QNAqld0SZ_po4qz5ZTHDxNxoZ4iw_T-4lhIBGm0RIZprDDGPI7Vo8qIeIMjZywoh_nq32zB6tnjEUBvHcgay0qXEnQkKkavzHO_c5sLc1qXM0jDQVqyO1enevW2yA_8gP0Qb7014ycN5umCvEHc66c2_iNT-R4zgw8gd1g05n2xwyET8qb_3wi5LqUV-Cri4mJ2xwGY8uynlD2I4jVtOYJusBgNs6AfwyehzsLdwSQ","e": "AQAB"},{"kty": "RSA","alg": "RS256","use": "sig","kid": "5a68fc8a3ec0c30e0be95aa08db99a68a725467f","n": "zmXvUwXYSo8VouhnkURp-3xywch-jPrk7q0gugqC7QIchBPnvdXdS-bj6sr1AqDl_hEDtiLGfiVr3Ft_U022rtHAl5n5NxyybUtZXWyT5yQZM4jopGBajavEUdCl9b4pqb-q_3fVaxUXe7re23sVjI5Bntd-8RYZ70tq-ZvCWBqsnz6lHi9Ditp3CZGWLMMBZlIv3nKnClOrZXL98Jmt7AAod-Gtk65saqnrMwWtBcI_Q-3u23ytywbMLanCeFFNUWlIOgZqyYYkOm-ylLRJzVaZ1THtcWILWCYUgxXjyF9DtXO3a8nct2JhdacD3LzRiPv3sXr31cg4arwUk19JoQ","e": "AQAB"}]}EOFsub google_login :Path('/google-login') Args(0) {my ($self, $c) = @_;requirePost($c);error($c, "Logging in via Google is not enabled.") unless $c->config->{enable_google_login};my $data = decode_jwt(token => ($c->stash->{params}->{id_token} // die "No token."),kid_keys => $googleKeys,verify_exp => 1,);die unless $data->{aud} eq $c->config->{google_client_id};die unless $data->{iss} eq "accounts.google.com" || $data->{iss} eq "https://accounts.google.com";die "Email address is not verified" unless $data->{email_verified};# FIXME: verify hosted domain claim?doEmailLogin($self, $c, "google", $data->{email}, $data->{name} // undef);}
[% IF c.user_exists %]<script>function finishSignOut() {$.post("[% c.uri_for('/logout') %]").done(function(data) {window.location.reload();}).fail(function() { bootbox.alert("Server request failed!"); });}function signOut() {[% IF c.user.type == 'google' %]gapi.load('auth2', function() {gapi.auth2.init();var auth2 = gapi.auth2.getAuthInstance();auth2.then(function () {auth2.signOut().then(finishSignOut, finishSignOut);});});[% ELSE %]finishSignOut();[% END %]}</script>[% ELSE %]<div id="hydra-signin" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="true"><form class="form-horizontal"><div class="modal-body"><div class="control-group"><label class="control-label">User name</label><div class="controls"><input type="text" class="span3" name="username" value=""/></div></div><div class="control-group"><label class="control-label">Password</label><div class="controls"><input type="password" class="span3" name="password" value=""/></div></div></div><div class="modal-footer"><button id="do-signin" class="btn btn-primary">Sign in</button><button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button></div></form></div><script>function finishSignOut() { }$("#do-signin").click(function() {requestJSON({url: "[% c.uri_for('/login') %]",data: $(this).parents("form").serialize(),type: 'POST',success: function(data) {window.location.reload();}});return false;});</script>[% IF c.config.enable_google_login %]<script>function onGoogleSignIn(googleUser) {requestJSON({url: "[% c.uri_for('/google-login') %]",data: "id_token=" + googleUser.getAuthResponse().id_token,type: 'POST',success: function(data) {window.location.reload();}});return false;};</script>[% END %][% END %][% IF c.config.enable_persona %]<script src="https://login.persona.org/include.js"></script><script>navigator.id.watch({onlogin: function(assertion) {requestJSON({url: "[% c.uri_for('/persona-login') %]",data: "assertion=" + assertion,type: 'POST',success: function(data) { window.location.reload(); },postError: function() { navigator.id.logout(); }});}});$("#persona-signin").click(function() {navigator.id.request({ siteName: 'Hydra' });});</script>[% END %]
You are signed in as <tt>[% HTML.escape(c.user.username) %]</tt>[% IF c.user.type == 'persona' %] via Persona[% END %].
You are signed in as <tt>[% HTML.escape(c.user.username) %]</tt>[%- IF c.user.type == 'persona' %] via Persona[%- ELSIF c.user.type == 'google' %] via Google[% END %].
<script>function doLogout() {[% IF c.user_exists %]$.post("[% c.uri_for('/logout') %]").done(function(data) {window.location.reload();}).fail(function() { bootbox.alert("Server request failed!"); });[% END %]}</script>[% IF c.user_exists && c.user.type == 'hydra' %]<script>$("#persona-signout").click(doLogout);</script>[% ELSIF personaEnabled %]<script src="https://login.persona.org/include.js"></script><script>navigator.id.watch({loggedInUser: [% c.user_exists ? '"' _ HTML.escape(c.user.username) _ '"' : "null" %],onlogin: function(assertion) {requestJSON({url: "[% c.uri_for('/persona-login') %]",data: "assertion=" + assertion,type: 'POST',success: function(data) { window.location.reload(); },postError: function() { navigator.id.logout(); }});},onlogout: doLogout});$("#persona-signin").click(function() {navigator.id.request({ siteName: 'Hydra' });});$("#persona-signout").click(function() {navigator.id.logout();});</script>[% END %][% IF !c.user_exists %]<div id="hydra-signin" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="true"><form class="form-horizontal"><div class="modal-body"><div class="control-group"><label class="control-label">User name</label><div class="controls"><input type="text" class="span3" name="username" value=""/></div></div><div class="control-group"><label class="control-label">Password</label><div class="controls"><input type="password" class="span3" name="password" value=""/></div></div></div><div class="modal-footer"><button id="do-signin" class="btn btn-primary">Sign in</button><button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button></div></form></div>
<script>$("#do-signin").click(function() {requestJSON({url: "[% c.uri_for('/login') %]",data: $(this).parents("form").serialize(),type: 'POST',success: function(data) {window.location.reload();}});return false;});</script>[% END %]
[% PROCESS auth.tt %]
[% IF personaEnabled %][% WRAPPER makeSubMenu title="Sign in" %]
[% WRAPPER makeSubMenu title="Sign in" %][% IF c.config.enable_google_login %]<li><a><div class="g-signin2" data-onsuccess="onGoogleSignIn" data-theme="dark"></div></a></li><li class="divider"></li>[% END %][% IF c.config.enable_persona %]
<input type="text" class="span3" name="emailaddress" [% IF !create && user.type == 'persona' %]disabled="disabled"[% END %] [%+ HTML.attributes(value => user.emailaddress) %]/>
<input type="text" class="span3" name="emailaddress" [% IF !create && user.username.search('@') %]disabled="disabled"[% END %] [%+ HTML.attributes(value => user.emailaddress) %]/>