Authentication API

API for secured requests.

The authentication API is compatible with the following standards:

[RFC 7519]
JSON Web Token (JWT)
[RFC 6750]
The OAuth 2.0 Authorization Framework: Bearer Token Usage
IIIF Authentication API 1.0
IIIF Authentication API 1.0. Edited by Michael Appleby, Tom Crane, Robert Sanderson, Jon Stroop, and Simeon Warner.
Contents

Endpoints Overview

Headless Authentication

Frontend Authentication

User Registration

Users register via a SAML service provider: /account/index.

The following SAML attributes are stored locally during registration:

If a home institution does not provide these required attributes, registration fails.

Headless Authentication

Authentication for non-user-driven clients.

Login Request

POST {scheme}://{server}/login

Example request:

POST /api.ka3.uni-koeln.de/login HTTP/1.1
Host: api.ka3.uni-koeln.de
{
  username: "user@institution.com",
  password: "123456"
}

Login Response

Response body is a JSON Web Token as described in RFC 6750.

Example response body:

{
  "username": "user@institution.com",
  "roles": [ROLE_MANAGER],
  "expires_in": 3600,
  "token_type": "Bearer",
  "refresh_token": "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00i",
  "access_token": "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00if"
}

Validate Token Request

POST {scheme}://{server}/validate

Example Request:

POST /api.ka3.uni-koeln.de/validate HTTP/1.1
Host: api.ka3.uni-koeln.de
Authorization: Bearer eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00if

Returns HTTP 200 if the token is still valid, HTTP 401 otherwise.

Logout

A logout request is not needed for headless authentication, as no state is kept in the server. Just remove the token from the client and let the token expire.

Frontend Authentication

SAML Single Sign-On authentication for user-driven clients.

Frontend authentication makes use of a secure session between a user client (SPA, Native App) and a SAML service provider in order to provide user clients JSON Web Tokens for REST API access.

Login Procedure

GET {scheme}://{server}/authentication/login?origin={frontend}[&messageId]

Establishes a session between a user client and a SAML service provider and provides a valid access token for REST API access.

See: https://iiif.io/api/auth/1.0/#login-interaction-pattern

Web Application
  1. User clicks the frontend's login button.
  2. Click opens a new child window pointing to the SAML Login URL and the origin of the frontend: window.open("https://ka3.uni-koeln.de/authentication/login?origin=https://frontend.example.com&messageId"). The messageId parameter prompts the server to respond with a web page instead of JSON.
  3. Since the SAML Login URL is a login protected resource, the child window redirects to the SAML Discovery Service: https://ka3.uni-koeln.de/samlDiscover.
  4. User selects his home institution and enters credentials into the login form of her / his home institutions' IdP.
  5. After successful login, the child window redirects back to the SAML Login URL https://ka3.uni-koeln.de/authentication/login?origin=https://frontend.example.com&messageId, which is a HTML page thanks to the messageId parameter. The HTML page contains the following HTML / JavaScript snippet:
    <div id="token" data-token="{"access_token": "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00if", ...}"></div>
    <script>
    document.addEventListener("DOMContentLoaded", function() {
        let token = document.getElementById("token").dataset.token;
        window.opener.postMessage(JSON.parse(token), "https://frontend.example.com");
    });
    </script>
  6. The JavaScript snippet sends the access token to the parent window by means of window.opener.postMessage(JSON.parse(token), "https://frontend.example.com"). Because of the Same-Origin-Policy, this only works if the origin parameter was set correctly in step 2.
  7. In order to receive the access token from the child window, the frontend must implement an event listener called "message":
    window.addEventListener("message", function(event) {
        //event.data contains the JWT
    }
  8. event.data contains the access token. After the "message" event is completed, the frontend continues in its individual way by storing the token in the session storage, for example. See below for a minimal example.

Token Procedure

GET {scheme}://{server}/authentication/token?origin={frontend}[&messageId]

Refreshes the session between the user client and the SAML service provider and sends the actual access token or a new access token, if the old has become invalid.

See: https://iiif.io/api/auth/1.0/#access-token-service

<iframe id="tokenFrame" src="https://ka3.uni-koeln.de/authentication/token?origin=https://frontend.example.com&messageId"></iframe>
<script>
setTimeout(function() {
    document.getElementById("tokenFrame").src = null;
    document.getElementById("tokenFrame").src = "https://ka3.uni-koeln.de/authentication/token?origin=https://frontend.example.com&messageId"
}, 20 * 60 * 1000);
</script>

Logout Procedure

GET {scheme}://{server}/saml/logout

Closes the session between the user client and the SAML service provider. In order to complete the logout, user clients should delete the local access token.

See: https://iiif.io/api/auth/1.0/#logout-service

Web Application
  1. User clicks the frontend's logout button.
  2. Click opens a new child window pointing to the SAML Logout URL: this.logoutWindow = window.open("https://ka3.uni-koeln.de/saml/logout").
  3. After the child window is loaded, client should check whether the server session was closed successfully.
  4. If session was closed, the client closes the logout window: this.logoutWindow.close().

JSON Web Token

{
  "access_token": "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00if",
  "token_type": "Bearer",
  "expires_in": "3600",
  "session_expires_in": "1800",
  "username": "gtest@uni-koeln.de",
  "cn": "Grails Test",
  "mail": "gtest@uni-koeln.de",
  "roles": ["ROLE_MANAGER", "ROLE_DEPOSITOR"]
}

The frontend JWT is slightly different from the headless JWT:

Minimal Web Example

<button id="loginButton">Login</button>
<button id="logoutButton" style="display:none;">Logout</button>
<div id="tokenView"></div>
<iframe id="tokenFrame" src="" style="display:none"></iframe>
<script>
    const self = this;
    function refreshToken() {
        let tokenFrame = document.getElementById("tokenFrame");
        tokenFrame.src = null;
        tokenFrame.src = "https://ka3.uni-koeln.de/authentication/token?messageId&origin=" + window.location.origin;
    }
    function refreshView() {
        if (sessionStorage.getItem("JWT")) {
            document.getElementById("loginButton").style.display = "none";
            document.getElementById("logoutButton").style.display = "";
            document.getElementById("tokenView").innerText = window.sessionStorage.getItem("JWT");
        } else {
            document.getElementById("loginButton").style.display = "";
            document.getElementById("logoutButton").style.display = "none";
            document.getElementById("tokenView").innerText = "";
        }
    }
    refreshView();
    document.getElementById("loginButton").addEventListener("click", function() {
        self.loginWindow = window.open("https://ka3.uni-koeln.de/authentication/login?messageId&origin=" + window.location.origin);
    });
    document.getElementById("logoutButton").addEventListener("click", function() {
        self.logoutWindow = window.open("https://ka3.uni-koeln.de/saml/logout");
        window.setTimeout(function() {
            refreshToken();
        }, 1000);
    });
    window.addEventListener("message", function(event) {
        if (event.data.hasOwnProperty("access_token")) { //login procedure or refresh session procedure return a valid
                                                         //access token (session is valid)
            sessionStorage.setItem("JWT", JSON.stringify(event.data));
            if (self.loginWindow) { //login procedure
                self.loginWindow.close();
            }
            refreshView();
        } else { //refresh session procedure does not return a access token (session is closed)
            sessionStorage.removeItem("JWT");
            if (self.logoutWindow) { //logout procedure
                self.logoutWindow.close();
            }
            refreshView();
        }
    });
    window.setInterval(function() { //refresh the session every 20 minutes
        refreshToken();
    }, 20 * 60 * 1000);
</script>

Resource Request

Authorization Request Header Field

The preferred way to request a secured resource with ajax is to send the access_token in the HTTP Authorization header:

Example
GET /api.ka3.uni-koeln.de/deposit/a5-1 HTTP/1.1
Host: api.ka3.uni-koeln.de
Authorization: Bearer eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00if

See: https://tools.ietf.org/html/rfc6750#section-2.1

URI Query Parameter

Alternatively, the access token can be sent in a query string parameter named access_token:

Example
GET /api.ka3.uni-koeln.de/deposit/a5-1?access_token=eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00if HTTP/1.1
Host: api.ka3.uni-koeln.de

See: https://tools.ietf.org/html/rfc6750#section-2.3

This is especially useful for HTML object rendering and streaming.