SSODemo

A Punk application signing users in through SAML 2.0, generated with punk new SSODemo and wired up with Punk::Plugin::SAML.

plackup app.psgi
# or, for the event loop Punk is built for:
hyperman app.psgi

What is here

| | | |---|---| | lib/SSODemo.pm | the routing table, and the three SAML declarations | | config/punk.yml | host, the session, and the plugin's own secret | | idp-metadata.xml | an example provider, so this boots with no network | | lib/SSODemo/Controller/Web/Root.pm | the front page and /me |

The plugin adds four routes of its own under /saml:

GET  /saml/metadata        this application's metadata
GET  /saml/login/:idp      start a login
GET  /saml/login           the same, when there is one provider
POST /saml/acs             where the provider answers

It requires https, and that is not a preference

config/punk.yml says host: https://localhost:5000. The identity provider answers by POSTing to /saml/acs from its own origin. That is a cross-site POST, and a cookie with SameSite=Lax is not sent on one - so the cookie remembering the login has to be SameSite=None, and browsers drop such a cookie unless it is also Secure.

Over plain http nothing fails at startup and every login fails at the assertion consumer with unsolicited. The plugin refuses to boot instead. To run this locally either use a local https certificate, or drive it with Punk::Test, which does not go through a browser.

Pointing it at a real provider

Two values go into the provider's console. Both come from this application's own metadata:

punk saml metadata

The AssertionConsumerService Location is the ACS URL - providers call it the Reply URL or the Single Sign-On URL. The entityID is the entity id - the Identifier, or the Audience URI. Give the provider both exactly as printed: it compares them character for character.

Their metadata URL comes back, and replaces the file:

saml_idp example => { metadata => 'https://.../metadata' };

Check it before starting the application:

punk saml idp https://.../metadata

which prints the entity id, the single sign-on URL, each signing certificate's fingerprint and the name id formats - or the reason the metadata was refused. A provider this plugin cannot use fails there, at a terminal, rather than at a user's first login.

Metadata is read once, at boot, before the server forks. A fetch that fails is a croak: an application whose only login is SAML cannot sign anyone in without its provider.

When a login fails

The browser gets "sign-in failed" and no reason, deliberately: a verifier that tells the far side which check it failed is telling an attacker which one to work on next. The code goes to the application log.

For the ticket that says SSO stopped working, save the POST body from the browser's network tab and ask:

punk saml verify saved-response.txt

which prints the first check that refused, with its code - audience, expired, unknown_issuer. The codes are listed in perldoc Punk::SAML::Error.

What this example does not show

Signing in for real, end to end. That needs a provider willing to sign an assertion, and idp-metadata.xml carries a certificate only - exactly as a real provider's metadata does, since nobody publishes their private key. t/01-basic.t drives everything up to the redirect and back down from the metadata route; Punk-SAML's own suite drives the other half against a fake provider it controls.

See also

perldoc Punk::Plugin::SAML