Skip to content

Authentication

The OJS REST API has no anonymous mode — every request must carry some identity. The server supports two paths: an API token, and login with a username and password. They have different requirements and different, real limitations — this page describes both, so choosing between them (or diagnosing why one of them isn't working) doesn't require reading the code.

In network mode (OJS_MCP_TRANSPORT=http), none of the server's environment variables below are taken into account — see Network mode: environment credentials are ignored below.

API token (OJS_API_TOKEN)

The recommended path — it doesn't depend on the login-attempt limit, doesn't touch the login form, and works even when the login page has a CAPTCHA (see below).

Requirement: api_key_secret in config.inc.php

OJS API tokens are signed with the api_key_secret key from the [security] section of the instance's config.inc.php. If that value is not set, OJS does not reject the token with a denial — it responds with a server error (500) to every request carrying a token, no matter how valid the token itself is. From this server's point of view that looks like an API outage, not a bad token — which is why it's worth checking this setting first, before suspecting the token or the account's permissions.

This setting is made by the OJS server administrator (someone with access to the instance's files); it cannot be turned on from the browser, nor from this MCP server.

Requirement: the web server must forward the Authorization header

Setting api_key_secret is not always enough. Apache does not hand the Authorization header to PHP on its own, so on a deployment that does not forward it, OJS never sees the token at all — the request arrives as anonymous and is refused with 401, no matter how valid the token is.

This failure is unusually hard to recognise, because the refusal is byte-for-byte identical to the one an anonymous request gets:

{"error":"You are not authorized to access the requested resource.","errorMessage":""}

Nothing in it mentions the header, so the natural suspects are the token or the account's roles in the journal — both of which are fine.

How to tell this apart from a genuine permission problem. OJS also accepts the token as a query parameter, and that path does not depend on the header. Compare the two against the same instance:

curl -s -o /dev/null -w '%{http_code}\n' \
     -H "Authorization: Bearer $TOKEN" \
     "https://journals.example.edu/index.php/index/api/v1/contexts"

curl -s -o /dev/null -w '%{http_code}\n' \
     "https://journals.example.edu/index.php/index/api/v1/contexts?apiToken=$TOKEN"

If the first gives 401 and the second 200, the token and the roles are correct and the header is being dropped in the web server. (If both give 500, it is api_key_secret — see above. If both give 401, the account genuinely has no role in that journal.)

The fix, made by the OJS server administrator, is one line in the Apache virtual host or .htaccess:

SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1

This affects the official pkpofficial/ojs Docker images too: as shipped, they drop the header, so an out-of-the-box container refuses every token until that line is added. The demo stack under demo/ in this repository mounts a patched vhost for exactly this reason.

This server always sends the token in the header and never in the query string — a token in a URL ends up in access logs, Referer headers and browser history, which is not an acceptable trade for working around a misconfigured web server.

How to generate a token

A logged-in user generates one in their own profile: User Profile → API Key (this tab is visible regardless of whether api_key_secret is set — its visibility is not the same thing as it working). The token acts with that account's permissions — its scope is whatever roles that account holds in the given journal, exactly as when logging in through the browser.

Takes precedence over login and password

When OJS_API_TOKEN is set, the server uses it exclusively — OJS_USERNAME/OJS_PASSWORD are then ignored (see Configuration).

Login and password (OJS_USERNAME / OJS_PASSWORD)

A fallback path for when api_key_secret can't be set (e.g. a shared instance, with no access to the server's files). The server replays the form-login sequence: it fetches a CSRF token from the login page, sends the username and password, and extracts the session token needed for subsequent requests from the resulting dashboard. This has two serious, practical limitations.

Won't work with reCAPTCHA or ALTCHA on the login page

If the instance has reCAPTCHA or ALTCHA enabled on its login page, the server detects this and aborts the sequence before sending the password. It doesn't attempt a "blind" login — sending the password without solving the CAPTCHA would fail on the OJS side anyway, while also consuming an attempt from the login-attempt limit (see below) for no benefit. The only way out in this situation is an API token.

Login-attempt limit — you can't just "keep trying"

OJS counts failed login attempts (RateLimitingService) regardless of who makes them. The server never loops on login — every 401 triggers exactly one retry, no more — but every attempt actually sent (including the first one) consumes the OJS quota the same way a manual browser login would. A few failed server startups with a wrong password can exhaust the limit on that account before anyone gets a chance to fix the configuration.

One message, three different causes

OJS's response doesn't distinguish why a login failed — a wrong password, an exhausted attempt limit, and a forced password change (mustChangePassword) on the account all look identical from the outside: no success redirect. The server doesn't guess which case it is — the error message lists all three possible causes at once, and figuring out which one actually applies requires logging into that same account through the browser.

Network mode: environment credentials are ignored

In OJS_MCP_TRANSPORT=http, the server has no identity of its own and can't have one — each client sends its own API token in the request header. The OJS_API_TOKEN, OJS_USERNAME, and OJS_PASSWORD variables set in the server process's environment are completely ignored in this mode (the server logs a warning about it at startup if they happen to be set — this is usually a sign that an .env file was copied from a stdio deployment without being cleaned up). Login with a username and password is not available at all in network mode — that path exists only for stdio mode, where the server handles one user at a time anyway. Details of the network mode security model are in Hosting.