aldine.

Aldine / Documentation / Single sign-on

Single sign-on for a self-hosted LaTeX editor

Aldine is an open-source, self-hosted collaborative LaTeX editor. It signs people in with your own OpenID Connect provider (Keycloak, Authentik, Authelia, Pocket ID, Dex), with Google, GitHub or ORCID, or with email and password. This page has every setting, the redirect URI and a recipe per provider. It also covers single sign-on on a self-hosted Overleaf, where it is part of Server Pro.

Applies to Aldine 0.11.0. Written by the Aldine project, so read it with that in mind. Statements about Overleaf are taken from the linked pages of Overleaf's own documentation. Checked on 12 October 2026.

Sign-in methods

Sign-in is off by default. Without AUTH_ENABLED an instance is single-tenant: there is no login, and everyone who can reach it has full access. AUTH_ENABLED=1 turns on accounts, per-project ownership and sharing (by invitation or by link).

MethodTurned on byNotes
Email and passwordAUTH_ENABLED=1Available as soon as sign-in is on. Passwords are at least 8 characters and stored as scrypt hashes. ALDINE_SSO_ONLY=1 switches it off.
OpenID ConnectOIDC_ISSUER and OIDC_CLIENT_IDOne provider per instance. Authorization code flow with PKCE; the client secret is optional.
GoogleGOOGLE_OAUTH_CLIENT_ID and GOOGLE_OAUTH_CLIENT_SECRETNeeds a verified email on the Google account.
GitHubGITHUB_LOGIN_CLIENT_ID and GITHUB_LOGIN_CLIENT_SECRETNeeds a verified email on the GitHub account. Separate from the OAuth app used for repository sync.
ORCIDORCID_CLIENT_ID and ORCID_CLIENT_SECRETThe account is keyed by the ORCID iD, so it works when the researcher's email is private.

Each provider is independent, and each one that is configured adds a button to the sign-in page. A session lasts 30 days.

Where the variables go

All configuration is environment variables on the app container. How they get there depends on which compose file you run (the self-hosting guide explains both):

An override file for the minimal install, with OpenID Connect as the example:

services:
  app:
    environment:
      AUTH_ENABLED: "1"
      ALDINE_PUBLIC_URL: https://aldine.example.com
      OIDC_ISSUER: https://auth.example.com/application/o/aldine/
      OIDC_CLIENT_ID: <client id>
      OIDC_CLIENT_SECRET: <client secret>
      OIDC_LABEL: University login
      ALDINE_ADMIN_EMAILS: you@example.org

The same settings in .env for docker-compose.full.yml:

AUTH_ENABLED=1
ALDINE_PUBLIC_URL=https://aldine.example.com
OIDC_ISSUER=https://auth.example.com/application/o/aldine/
OIDC_CLIENT_ID=<client id>
OIDC_CLIENT_SECRET=<client secret>
OIDC_LABEL=University login

In .env, give each comment its own line. Anything after a value becomes part of the value, so AUTH_ENABLED=1 multi-user login is not 1 and sign-in stays off without an error.

The redirect URI

Register exactly this redirect (callback) URI at the provider:

<ALDINE_PUBLIC_URL>/api/auth/oauth/<provider>/callback

<provider> is oidc, google, github or orcid. For OpenID Connect on aldine.example.com:

https://aldine.example.com/api/auth/oauth/oidc/callback

When Aldine is served under a path prefix, ALDINE_PUBLIC_URL carries the prefix and so does the URI, for example https://server.example.com/internal/aldine/api/auth/oauth/oidc/callback. Without ALDINE_PUBLIC_URL, Aldine derives the URI from the host of the request, which behind a proxy is often not what the provider has on file. Set it.

General settings

VariableDefaultMeaning
AUTH_ENABLEDunset1 or true turns on login, ownership and sharing. Unset: single-tenant, no login.
ALDINE_PUBLIC_URLunsetThe public URL of the instance, with the path prefix if there is one. Used for the redirect URI and for password-reset links. When it starts with https, session cookies get the Secure flag.
ALDINE_SSO_ONLYunset1 or true disables registration, password sign-in, password reset and password change. Sign-in goes through the configured providers only.
ALDINE_ADMIN_EMAILSunsetComma-separated addresses that may open /admin. Compared without regard to case. See administrators.

OpenID Connect with your own identity provider

OpenID Connect needs AUTH_ENABLED=1 and ALDINE_PUBLIC_URL. OIDC_ISSUER and OIDC_CLIENT_ID turn it on. It appears on the sign-in page as one more button, labelled "Continue with" and the value of OIDC_LABEL.

VariableDefaultMeaning
OIDC_ISSUERnone, requiredThe issuer URL as the provider publishes it. Aldine reads <issuer>/.well-known/openid-configuration. An issuer with a path works; keep the trailing slash if the provider shows one. Must be https, except for localhost.
OIDC_CLIENT_IDnone, requiredThe client ID you registered.
OIDC_CLIENT_SECRETunsetSet it for a confidential client: Aldine authenticates with client_secret_basic, or with client_secret_post when the discovery document offers only that. Leave it unset for a public client, which uses PKCE only. PKCE (S256) is used either way.
OIDC_LABELSingle sign-onThe button text and the provider's name on the account page, for example Keycloak or University login.
OIDC_SCOPESopenid email profileScopes to request, separated by spaces or commas. openid is always added. Add groups for providers that send groups only when asked (Authelia, Pocket ID).
OIDC_ALLOWED_GROUPSunsetComma-separated group names. When set, only members of at least one of them may sign in with OpenID Connect. Unset: everyone the provider authenticates gets in. It does not affect password accounts; see access control.
OIDC_GROUPS_CLAIMgroupsThe claim that lists the groups. Read from the ID token, or from the userinfo endpoint when the ID token has none. A single string counts as one group.
OIDC_EMAIL_VERIFIEDrequirerequire: Aldine uses the address only when the provider says email_verified: true. trust: it uses whatever address the provider sends. Read accounts and email addresses before choosing trust.

At start, the log names the issuer, the client ID and whether the client is public or confidential. The secret is never printed:

[aldine] OIDC sign-in: issuer=https://auth.example.com/application/o/aldine/ client=aldine (confidential)

When some OIDC_* variables are set but OIDC_ISSUER or OIDC_CLIENT_ID is missing (a typo such as OIDC_CLIENTID), the log says which one and the feature stays off. A value of OIDC_EMAIL_VERIFIED other than require or trust is logged and treated as require.

The discovery document is fetched when Aldine starts and cached for an hour. When the hourly refresh fails, Aldine keeps the last good document and retries every 15 seconds. A provider that is down does not stop Aldine or the other sign-in methods.

A private CA or a self-signed certificate

Aldine checks the provider's TLS certificate against Node's built-in CA list. If the provider uses an internal CA, mount the CA certificate into the app container and point NODE_EXTRA_CA_CERTS at it:

services:
  app:
    environment:
      NODE_EXTRA_CA_CERTS: /etc/aldine/ca.pem
    volumes:
      - ./ca.pem:/etc/aldine/ca.pem:ro

What Aldine checks

Provider recipes

Replace aldine.example.com with your ALDINE_PUBLIC_URL. A recipe that was checked against a running copy of the provider says so; the others follow the provider's documentation.

Keycloak

Verified against Keycloak 26.0 on 23 September 2026 (confidential client).

  1. In your realm: Clients → Create client. Client type OpenID Connect, Client ID aldine.
  2. Capability config: Client authentication on (confidential) and Standard flow on; everything else off.
  3. Login settings: Valid redirect URIs https://aldine.example.com/api/auth/oauth/oidc/callback, Web origins https://aldine.example.com.
  4. Credentials tab: copy the client secret.
  5. Optional, for OIDC_ALLOWED_GROUPS: Clients → aldine → Client scopes tab → aldine-dedicated → Add mapper → By configuration → Group Membership. Token claim name groups, Full group path off (otherwise the claim holds /aldine-users and nobody matches), Add to ID token on. With Add to ID token off and Add to userinfo on, Aldine reads the groups from userinfo.
OIDC_ISSUER=https://keycloak.example.com/realms/<realm>
OIDC_CLIENT_ID=aldine
OIDC_CLIENT_SECRET=<client secret>
OIDC_LABEL=Keycloak

Keycloak sends email_verified from the user's Email verified switch, and users created by an admin start unverified. Turn the switch on before they first sign in to Aldine, or give them the Verify Email required action.

Authentik

Verified against Authentik 2026.8.3 on 23 September 2026 (confidential and public client).

  1. Applications → Providers → New Provider → OAuth2/OpenID Provider → Next. Name Aldine, Authorization flow one of the default-provider-authorization-…-consent flows. Client type Confidential, or Public if you will not use a secret. Under Redirect URIs/Origins, Add entry: Strict, https://aldine.example.com/api/auth/oauth/oidc/callback.
  2. Signing Key: keep authentik Self-signed Certificate, or pick another RSA or EC certificate. Without a signing key, Authentik signs ID tokens with HS256 and the client secret, which Aldine refuses.
  3. Grant types: Aldine needs only Authorization Code. A provider created through a blueprint or the API has no grant types unless you list them (grant_types: [authorization_code]), and Authentik then answers every sign-in with invalid_request. A blueprint or API call must also list the scope mappings (goauthentik.io/providers/oauth2/scope-openid, -email, -profile) and the signing key; the form fills in both.
  4. Applications → Applications → Create: name Aldine, slug aldine, provider from step 1.
  5. Copy the client ID and secret from the provider.
OIDC_ISSUER=https://auth.example.com/application/o/aldine/
OIDC_CLIENT_ID=<client id>
# leave the secret out for a public client
OIDC_CLIENT_SECRET=<client secret>
OIDC_LABEL=Authentik

The issuer ends in the application slug and a slash; the provider page shows it as OpenID Configuration Issuer. Renaming the slug changes the issuer (see changing the issuer). The default profile scope mapping sends a groups claim with the group names, so OIDC_ALLOWED_GROUPS works without extra setup.

The default email mapping sends email_verified: false for everyone, so with the default OIDC_EMAIL_VERIFIED=require people sign in to accounts without an email address. To give accounts their addresses, either set OIDC_EMAIL_VERIFIED=trust, or create a scope mapping for scope email that returns {"email": request.user.email, "email_verified": True} and select it on the provider in place of the default email mapping. Both amount to trusting Authentik's addresses. That is safe only while users cannot change their own address: keep Allow users to change email off under System → Settings (off by default) and do not grant goauthentik.io/user/can-change-email in a group's attributes.

Authelia

Verified against Authelia 4.39.28 on 23 September 2026 (confidential client; email, name and groups came from userinfo).

In configuration.yml (Authelia 4.38 or later), with the secret stored as a digest (authelia crypto hash generate pbkdf2 --variant sha512):

identity_providers:
  oidc:
    clients:
      - client_id: 'aldine'
        client_name: 'Aldine'
        client_secret: '$pbkdf2-sha512$310000$...'
        public: false
        authorization_policy: 'two_factor'
        require_pkce: true
        pkce_challenge_method: 'S256'
        redirect_uris:
          - 'https://aldine.example.com/api/auth/oauth/oidc/callback'
        scopes: ['openid', 'email', 'profile', 'groups']
        response_types: ['code']
        grant_types: ['authorization_code']
        token_endpoint_auth_method: 'client_secret_basic'
OIDC_ISSUER=https://auth.example.com
OIDC_CLIENT_ID=aldine
# the secret itself, not the digest
OIDC_CLIENT_SECRET=<client secret>
OIDC_SCOPES=openid email profile groups
OIDC_LABEL=Authelia

Authelia 4.39 and later keep email, name and groups out of the ID token and serve them from the userinfo endpoint; Aldine reads them from there. two_factor stops everyone who has not registered a second factor at Authelia; use one_factor to try the setup first. Authelia only serves https, so an Authelia with a self-signed or internal certificate also needs NODE_EXTRA_CA_CERTS.

Pocket ID

  1. Administration → OIDC Clients → Add OIDC client. Name Aldine, callback URL https://aldine.example.com/api/auth/oauth/oidc/callback. Turn on PKCE. Turn on Public client only if you will not use a secret.
  2. Copy the client ID and, for a confidential client, the secret (shown once).
  3. Optional: restrict the client to user groups in Pocket ID itself, or list them in OIDC_ALLOWED_GROUPS.
OIDC_ISSUER=https://id.example.com
OIDC_CLIENT_ID=<client id>
# leave the secret out for a public client
OIDC_CLIENT_SECRET=<client secret>
OIDC_SCOPES=openid email profile groups
OIDC_LABEL=Pocket ID

The issuer is Pocket ID's public URL (APP_URL) with no path. Pocket ID reports addresses as verified, including ones users set on their own account page. Before relying on the address for invites or ALDINE_ADMIN_EMAILS, turn off self-service account editing in Pocket ID's Application Configuration.

Dex

Verified against Dex v2.41.1 on 23 September 2026 (public and confidential client, static password connector).

In Dex's config.yaml:

staticClients:
  - id: aldine
    name: Aldine
    # leave the secret out and set `public: true` for a public client
    secret: <client secret>
    redirectURIs:
      - 'https://aldine.example.com/api/auth/oauth/oidc/callback'
OIDC_ISSUER=https://dex.example.com/dex
OIDC_CLIENT_ID=aldine
# leave the secret out for a public client
OIDC_CLIENT_SECRET=<client secret>
OIDC_LABEL=Dex

The issuer is Dex's issuer setting, usually with no trailing slash. Groups come only from connectors that provide them (LDAP, GitHub and others) and only with the groups scope (OIDC_SCOPES=openid email profile groups). The static password connector sends none, so OIDC_ALLOWED_GROUPS turns everyone away with it.

Google, GitHub and ORCID

Each of the three needs both its client ID and its client secret; with only one of them set, the button does not appear. Register the redirect URI from above with the provider's name in it.

ProviderVariablesRedirect URIWhere to create the client
GoogleGOOGLE_OAUTH_CLIENT_ID
GOOGLE_OAUTH_CLIENT_SECRET
<ALDINE_PUBLIC_URL>/api/auth/oauth/google/callbackGoogle Cloud console, APIs and services, Credentials: an OAuth client of type "Web"
GitHubGITHUB_LOGIN_CLIENT_ID
GITHUB_LOGIN_CLIENT_SECRET
<ALDINE_PUBLIC_URL>/api/auth/oauth/github/callbackGitHub, Settings, Developer settings: an OAuth app
ORCIDORCID_CLIENT_ID
ORCID_CLIENT_SECRET
ORCID_SANDBOX (optional)
<ALDINE_PUBLIC_URL>/api/auth/oauth/orcid/callbackORCID developer tools: a Public API client. The callback must be HTTPS.

Email and password

With AUTH_ENABLED=1 and nothing else, people register with an email address and a password of at least 8 characters. Registration does not verify the address.

Password-reset links are sent by email when a mail transport is configured: SMTP_HOST, SMTP_PORT (default 587), SMTP_USER, SMTP_PASS and SMTP_FROM, with SMTP_SECURE=1 for implicit TLS, or SES_FROM with AWS_REGION for AWS SES. The link is built from ALDINE_PUBLIC_URL and expires after one hour. Without a transport, the reset token is written to the server log for the administrator to pass on.

ALDINE_SSO_ONLY=1 disables all of this. No mail transport is needed then.

Accounts and email addresses

An OpenID Connect account is keyed by the issuer and the provider's sub, not by the email address. Signing in again finds the same account even after the address changes at the provider.

Choose OIDC_EMAIL_VERIFIED=trust only when users cannot set or change their own email address at the provider. Otherwise anyone who can edit their profile there can pick any address. The same applies to a provider that reports email_verified: true for every address, including ones users typed in themselves.

Changing the issuer

Because the account is keyed by the issuer URL exactly as the provider spells it, any change of that URL gives everyone new identities: a new host name, a renamed Authentik application slug, or a Keycloak upgrade that dropped the /auth prefix. If you can, keep the old issuer URL. The procedure for moving accounts onto a new issuer is in docs/OIDC.md.

Access control

Administrators

ALDINE_ADMIN_EMAILS is a comma-separated list of addresses. A signed-in account whose email is on the list can open /admin: accounts, active users, projects and compile time. It shows metadata only. There is no role stored in the database; the list in the environment is the whole configuration.

Troubleshooting

Every sign-in failure ends on a short Aldine page titled "Sign-in failed" with the reason and a "Back to sign-in" link. The messages below are the start or the middle of that reason.

MessageCause
This sign-in provider is not configuredAUTH_ENABLED is off, or the variables of that provider are not all set.
is unavailable: the identity provider at … could not be reached (…)Aldine cannot fetch <issuer>/.well-known/openid-configuration: a firewall between Aldine and the provider, a wrong port, or the provider is down. The bracket holds the network error (ECONNREFUSED, ETIMEDOUT). Failed lookups are retried after 15 seconds.
presented a TLS certificate that is not trusted (…)The provider's certificate is self-signed or from an internal CA. Set NODE_EXTRA_CA_CERTS. ERR_TLS_CERT_ALTNAME_INVALID means the certificate is for another host name.
could not be found (ENOTFOUND): its host name does not resolveThe host in OIDC_ISSUER does not resolve from the Aldine server or container (an internal name, or a typo).
answered with a redirectOIDC_ISSUER redirects, often from http to https or because of a missing path. Use the URL it redirects to.
the discovery document names issuer "…", not …OIDC_ISSUER differs from the issuer the provider publishes. Copy it from the provider.
must use httpsThe issuer or an endpoint in the discovery document is plain http on a host other than localhost.
signs ID tokens only with algorithms Aldine does not acceptThe provider signs with HS256 (Authentik without a signing key). Give it an RSA or EC key.
the ID token signature could not be verifiedThe token is signed with a key or algorithm that is not in the provider's JWKS or discovery document.
the identity provider's signing keys could not be fetched from …Discovery worked, but the JWKS URL did not answer, answered with an error or a redirect, or timed out. Check that Aldine can reach that URL.
the ID token is dated in the future (iat claim)The provider's clock is more than a minute ahead of Aldine's. Sync both with NTP.
the ID token has expired (… claim)The token is past its exp, or more than 10 minutes old by Aldine's clock: Aldine's clock runs ahead of the provider's, or the sign-in took very long.
the response did not name its identity provider (iss parameter missing)The provider advertises the RFC 9207 iss parameter but the callback had none; something between the provider and Aldine rewrote the redirect.
the ID token was rejected (aud claim)OIDC_CLIENT_ID is not the client the token was issued to.
the identity provider refused the sign-in (invalid_client …)Wrong OIDC_CLIENT_SECRET, or a secret set for a public client (or the other way round).
your account is not in a group that may use this Aldine instanceOIDC_ALLOWED_GROUPS is set and the person's groups claim names none of them. Check OIDC_GROUPS_CLAIM, and that the provider sends groups (Keycloak needs a mapper; Authelia and Pocket ID need the groups scope).
OAuth state mismatchThe sign-in took more than 10 minutes, cookies are blocked, or the callback was opened in another browser.
An account with this email already existsThe address belongs to a password account or to an account from another provider. Single sign-on never takes it over; the message names the method to use.
An account with this email already belongs to a different single sign-on identityUsually OIDC_ISSUER changed. See changing the issuer.
no verified email on the Google account or no verified email on the GitHub accountThe provider did not report a verified address. Verify the address there and sign in again.
Password sign-in is disabledALDINE_SSO_ONLY is set, so the password endpoints answer 403.
[aldine] OIDC sign-in is off: OIDC_CLIENT_ID is not set in the boot logSome OIDC_* variables are set but not both required ones. Check the spelling.
The provider reports a redirect_uri mismatchThe redirect URI at the provider differs from <ALDINE_PUBLIC_URL>/api/auth/oauth/oidc/callback. Check the path prefix and trailing slashes.

Single sign-on on Overleaf: Community Edition and Server Pro

If you came here looking for SSO on a self-hosted Overleaf, this is what Overleaf's documentation says.

The SAML 2.0 and LDAP pages do not mention OpenID Connect, and we found no Overleaf page that documents it for either on-premises edition (checked 12 October 2026).

The two products cover different protocols. Aldine documents OpenID Connect and the OAuth providers on this page, not SAML or LDAP. If your institution offers only SAML or LDAP, Server Pro is the documented fit. If it runs Keycloak, Authentik or another provider that speaks OpenID Connect, the recipes above apply. The wider comparison is on self-hosting Overleaf (Community Edition, Server Pro) compared with Aldine.

What is not supported

Try the live demo View source on GitHub

No account, no install. The demo is a shared sandbox without sign-in and resets every night.

Common questions

Does Overleaf Community Edition support single sign-on?

No. Overleaf's comparison table lists "Single sign-on (SSO) via SAML 2 and LDAP" for Server Pro and not for Community Edition. (Server Pro vs. Community Edition, checked 12 October 2026)

Which redirect URI do I register for Aldine?

<ALDINE_PUBLIC_URL>/api/auth/oauth/<provider>/callback, where the provider is oidc, google, github or orcid. For OpenID Connect on aldine.example.com that is https://aldine.example.com/api/auth/oauth/oidc/callback.

Can I turn passwords off and use only single sign-on?

Yes. Set ALDINE_SSO_ONLY=1. Registration, password sign-in, password reset and password change are then refused, and sign-in goes through the configured providers only.

Does Aldine support SAML or LDAP?

No. Aldine signs people in with OpenID Connect, Google, GitHub, ORCID or email and password. An identity provider that speaks both SAML and OpenID Connect, such as Keycloak or Authentik, can be connected over OpenID Connect.