> ## Documentation Index
> Fetch the complete documentation index at: https://docs.publica.la/llms.txt
> Use this file to discover all available pages before exploring further.

# SAML 2.0

## Overview

SAML (Security Assertion Markup Language) lets an institution authenticate its readers against its own identity provider and send them into your store already logged in. The institution acts as the identity provider (IdP); your Publica.la store acts as the service provider (SP). Readers never share their institutional credentials with you.

Each reader gets their own account.



Identity providers known to work with this integration include Google Workspace, OpenAthens, Azure AD, and Shibboleth.

:::tip
For the step-by-step walkthrough of the dashboard screens, with screenshots, see the [Help Center article](https://help.publica.la/es/article/autenticar-usuarios-a-traves-de-saml-20-jpylkt/).
:::

## Pre-requisites

1. An Enterprise plan subscription.
2. The institution's identity provider metadata XML file.

## How it works

Both directions of SAML single sign-on are supported.

- **IdP-initiated**: the reader clicks your store in their institutional portal. The identity provider posts a signed assertion to your store, which validates it and starts the session.
- **SP-initiated**: the reader opens the login URL of the integration. Your store redirects them to the identity provider, they authenticate there, and the provider posts the assertion back.

Each integration you create gets its own set of endpoints, named after the consumer key you choose:

| Endpoint                                                     | Role                                                  |
| ------------------------------------------------------------ | ----------------------------------------------------- |
| `https://{store_final_domain}/saml2/{consumer_key}/login`    | SP-initiated login. Give this URL to the institution. |
| `https://{store_final_domain}/saml2/{consumer_key}/metadata` | Your store's SAML metadata, as XML.                   |
| `https://{store_final_domain}/saml2/{consumer_key}/acs`      | Assertion consumer service. Where the IdP posts.      |
| `https://{store_final_domain}/saml2/{consumer_key}/sls`      | Single logout service.                                |

## Configuration

Ask the institution for the following values and load them in `Settings > Integrations > SAML settings`:

| Field                  | Description                                                                        |
| ---------------------- | ---------------------------------------------------------------------------------- |
| Consumer key           | A name you choose to identify the institution, for example `universidad-san-blas`. |
| Entity id              | The identity provider's entity id.                                                 |
| Single sign-on service | URL the reader is redirected to in order to authenticate.                          |
| Single logout service  | URL used to end the session on the identity provider.                              |
| x509 certificate       | Certificate used to validate the signature of the assertion.                       |

Every value except the consumer key comes from the identity provider's metadata XML file.

You also choose whether readers get direct access to all content or a plan assigned on login, the same distinction described in [Authentication by IP Ranges](./auth-ip-integrations-tenants.md).

Once saved, the integration shows a **login URL** and a **metadata URL**. The institution registers your store as an application in their identity provider using the values published at that metadata URL:

| Value the IdP asks for               | Where it comes from                              |
| ------------------------------------ | ------------------------------------------------ |
| Login URL                            | Shown in the integration details.                |
| Entity id, or audience               | `md:EntityDescriptor.entityID` in your metadata. |
| Assertion consumer service (ACS) URL | `md:AssertionConsumerService.Location`.          |
| Single logout service URL            | `md:SingleLogoutService.Location`.               |
| x509 certificate                     | `ds:X509Certificate`.                            |

## Identifying the reader

The assertion can identify a reader by email address, by an institutional identifier, or by both. Either one on its own is enough, so institutions that do not release email addresses can still be integrated.

### Email address

Taken from the first of these that holds a valid email address: the NameID, the Azure `emailaddress` claim, the `emailAddress` attribute (the one we recommend, and the one OpenAthens sends), or the `mail` friendly name (`urn:oid:0.9.2342.19200300.100.1.3`).

### External id

Taken from the first of these that holds a usable value:

1. The standard attributes `urn:oid:1.3.6.1.4.1.5923.1.1.1.6`, `eduPersonPrincipalName`, `eduPersonTargetedID`, and the Azure `nameidentifier` claim.
2. The NameID, when it is not an email address.

These names are fixed. If your institution publishes its identifier under a name of its own, contact support.

The value can be up to 200 characters. A NameID in `transient` format is never used as an identifier, because the identity provider regenerates it on every session and each login would create a new account.

Any other attribute in the assertion is ignored, with one exception: `reader_exit_url`, which sets where the reader's exit button in the reader takes them.

### How the account is resolved

- The reader is looked up by external id first, then by email address. If neither matches an existing account, one is created from the values the assertion carries, and an account created from an external id alone has no email address.
- If the account is found by email and has no external id yet, the external id is attached to it. This links readers that were created from the dashboard beforehand.
- The reader is sent to the login form only when the assertion carries no usable identifier at all.

The external id identifies one account across integrations, so a reader arriving through SAML and through an [IP range entry point](./auth-ip-integrations-tenants.md) with the same identifier lands on the same account.

## Troubleshooting

| Symptom                                         | Likely cause                                                                                | Recommended fix                                                                           |
| ----------------------------------------------- | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| Reader lands on the login form                  | The assertion carries neither an email address nor an external id                           | Ask the institution to release the identifier under one of the attributes listed above.   |
| A new account is created on every login         | The identity provider sends a `transient` NameID and no attributes                          | Ask the institution to release a stable attribute.                                        |
| Readers share a single account                  | The identity provider sends the same identifier for every reader                            | Confirm the attribute is per reader, not per institution.                                 |
| A reader sees an empty library after logging in | The identifier the identity provider sends already belongs to another account in your store | Contact support with the reader's email address and the identifier the assertion carries. |
| The store rejects the assertion                 | Certificate mismatch or an expired certificate                                              | Request the current x509 certificate from the institution and update the entry point.     |

To follow the SAML messages exchanged in the browser, the [SAML-tracer extension](https://addons.mozilla.org/en-US/firefox/addon/saml-tracer/) shows each request and response as it happens.
