5.2. Administrative Interfaces#

Tokens may be managed via an HTTP API. The main API to manage tokens is located at the controller /admin - see documentation for AdminController class: linotp.controllers.admin.AdminController.

The admin controller provides functions to manage tokens:

  • Enroll token: /admin/init

  • Enable token: /admin/enable

  • Disable token: /admin/disable

  • List tokens: /admin/show

  • Delete tokens: /admin/delete

Other managing controllers are:

  • /system: provides the functions to configure the LinOTP server, e.g. resolver, policies or provider.

  • /manage: interfaces used for the Web UI.

  • /audit: provides the functions to query the audit trail.

For more details on the managing controller interfaces, have a look at the LinOTP API documentation https://linotp.org/doc/api/linotp.controllers.html

Note

Every management controller supports the parameter httperror followed by an HTTP error code. LinOTP returns HTTP 200/OK with status: false in the JSON response indicating an internal error. This parameter lets you override the HTTP status code that is returned to indicate an internal error, e.g. to return an HTTP 500 error code instead.

5.2.1. Authentication#

Starting with version 3.2, LinOTP directly restricts access to the managing controllers to authenticated users. Authentication happens within LinOTP, and it is no longer necessary to use Apache for authentication.

To authenticate a user, the API endpoint /admin/login/ is called with the parameters ‘username’ and ‘password’. Upon valid authentication, the user receives a JSON Web Token (jwt) in a cookie, which will be used for further managing controller access.

Since user authentication is now done within LinOTP, the user must be part of the admin realm. The name of the admin realm can be defined by the LinOTP configuration entry ‘ADMIN_REALM_NAME’. By default, the realm name is ‘linotp_admins’. The admin realm by default contains an admin resolver ‘LinOTP_local_admins’ - the name can be changed by the configuration entry ‘ADMIN_RESOLVER_NAME’.

To bootstrap the admin authentication, a user can be added to the ‘LinOTP_local_admins’ resolver using the LinOTP command line tool:

$ linotp local_admins add <admin user name>

and setting the password for the admin user:

$ linotp local_admins password <admin user name>

For more details about the usage of the LinOTP command line tool you can type

$ linotp local-admins --help

Usage of the administrative API#

The LinOTP administrative API (e.g. /manage, /admin, /system) requires the user to be authenticated. With a valid authentication, the user recieves a JSON Web Token (jwt) which is provided in the cookie access_token_cookie. In addition to the access token an additional cookie csrf_access_token is returned, which is used for CSRF (Cross Site Request Forgery) protection.

With LinOTP 3.2 the access to administrative interfaces that are not server state changing do not require a session parameter anymore. Examples for those interfaces are /system/getConfig or /admin/show.

Other interfaces, for example the /admin/init endpoints, that are server state changing operation require the cookie value of the csrf_access_token as HTTP header “X-CSRF-TOKEN” within a HTTP POST request.

The usage of POST for state changing and GET for non-state changing operations has its roots in the HTTP 1.1 specification. Please have a look at the LinOTP API documentation when to use which HTTP request method: https://linotp.org/doc/api/linotp.controllers.html

5.2.2. Accessing the API with your Browser#

With LinOTP 3.2 it’s easy to gather information directly in the browser as no session parameter is required for these GET requests anymore.

For other, server state changing operations, direct browser interaction is not possible anymore and requires dedicated tools like Insomia.

or a programmatic access to the LinOTP API.

5.2.3. Programmatically calling the API#

To call the administrative API from a script, you need to use a programming language/library that allows you to submit HTTPS requests where you can set cookies and HTTP headers.

Below is a fully functional Python example using the requests library to get a list of all token (GET) and finally creates a token (POST):

# -*- coding: utf-8 -*-
"""
Small example on how to use the LinOTP administrative API.
"""
import os
import requests
from urllib.parse import urljoin

# Adapt this for your server
BASE_URL = "https://<your LinOTP server url>"

ADMIN_USER = "admin"
ADMIN_PWD = "Test123!"

# ------------------------------------------------------------
# authenticate the admin user

session = requests.Session()

response = session.post(
    urljoin(BASE_URL, "/admin/login"),
    data={
        "username": ADMIN_USER,
        "password": ADMIN_PWD,
    },
)
assert response.status_code == 200

# ------------------------------------------------------------
# run a non state changing GET request /admin/show

response = session.get(urljoin(BASE_URL, "/admin/show"))

# Verify the response contains the expected data
assert response.status_code == 200

result = response.json()
assert result["result"]["status"] == True

token_list = result["result"]["value"]["data"]

# Print a list of all token serial numbers
print(f"--> Found {len(token_list)} tokens:")
for token in token_list:
    print(token["LinOtp.TokenSerialnumber"])

# ------------------------------------------------------------
# run a state changing POST request /admin/init (requires header)

session.headers.update(
    {
        "X-CSRF-TOKEN": session.cookies.get("csrf_access_token"),
    }
)

response = session.post(
    urljoin(BASE_URL, "/admin/init"),
    data={
        "type": "pw",
        "otpkey": "geheim1",
    },
)

# Verify the response contains the expected data
assert response.status_code == 200

result = response.json()
assert result["result"]["status"] == True

print(f"\n--> Token with serial {result["detail"]['serial']} created")

5.2.4. Technical background#

LinOTP 3.2 checks the user authorization when accessing the administrative API (e.g. /admin, /system) on its own and does not rely on the web server base authentication anymore. The authorisation information is held in the cookie access_token_cookie.

In addition to the access_token_cookie there is a second cookie csrf_access_token which is used to implement CSRF protection.

See CSRF on Wikipedia for more information.

LinOTP implements a common solution to this problem by requiring the client to send some information that would otherwise not be sent automatically by the browser. This obviously cannot be a cookie (because the browser sends cookies with every request automatically); viable contenders include custom HTTP headers and custom form parameters. The value of this header or parameter could be either something that the server decrypts and verifies (e.g., a timestamp which expires after a certain time) or it can simply be something that is inaccessible to an attacker, like a cookie. (We assume that an attacker may lure you onto a malicious web site but has no control over your browser, which means they cannot see cookies for domains other than their own.) Therefore if LinOTP requires you to pass the value of a cookie in an HTTP header, this is something an attacker cannot do.

LinOTP 3.2 implements this approach by using a csrf_access_token header whose value is derived from the cookie access_token_cookie.

5.2.5. Orphaned tokens#

Orphaned tokens are tokens which are assigned to a user without a corresponding entry in the user resolver (presumably because the entry was deleted in the meantime).”

Using the method /admin/show you can also view such orphaned tokens by calling the method:

/admin/show?user='/:no%20user%20info:/'

You can also find tokens that have no users or tokens that are assigned to no realm:

/admin/show?user=''

/admin/show?realm=''