Other applications — a CMS, an admin panel, a customer portal — can sign a visitor into FileGator without a password. The application builds a short, signed URL; the browser follows it; FileGator opens a normal session.

No password crosses the boundary and no API keys are handed out. Each application has its own secret, so one can be revoked without touching the others.

How it works

panel                       browser                    FileGator
  |  build signed URL  ->      |                            |
  |                            |  GET /auth/sso?...    ->   |  verify signature
  |                            |                            |  spend the nonce
  |                            |                            |  create session cookie
  |                            |  <- 303 to /               |
  |                            |  normal browsing       ->  |

The signed URL is a ticket, not a session:

Lifetime
Ticket (/auth/sso?...)60 seconds, single use
Session it createssecurity.session_ttl, 8 hours by default

Once exchanged, the ticket is spent. Replaying it returns 403 and issues nothing, so a URL leaked through browser history or a proxy log is worthless.

Configure an application

{
  "sso": {
    "applications": [
      {
        "name": "panel",
        "secret": "0123456789abcdef0123456789abcdef",
        "mode": "create",
        "home": "/clients/%s",
        "permissions": ["read", "download", "upload"],
        "admin_usernames": [],
        "expiry": 60
      }
    ]
  }
}

Generate a secret with openssl rand -hex 32. It must be at least 32 characters.

KeyMeaning
nameIdentifier the handoff must carry (app=panel). Unique, and required
secretShared with the application. At least 32 characters, required. Never returned by any API
modeexisting, policy or create — see below. Required
homeHome template; %s becomes the username. Required for policy and create; ignored by existing, which uses the account's own home
permissionsGranted in policy, and to accounts made by create. Ignored by existing
admin_usernamesAdministrators inside this application's sessions only. Empty is normal
expiryTicket lifetime in seconds, 0–3600. 0 means the default of 60, not "no expiry"

A configuration that fails any of these checks stops startup with the application's name and the reason, rather than failing at the first handoff.

The three modes

modeUsername FileGator already knowsUsername it does not know
existingsigns in as that user, with their own permissions and home403
policysigns in with the application's permissions and home templatesame — the account need to exist
createsigns in with their own permissionsaccount created from the policy, then signs in

existing is the safest: the remote application can only open sessions for people you created, and can never widen what they can do. It cannot grant administrator unless you list the name in admin_usernames.

policy is for handing someone into a folder rather than an account. For a customer portal, "home": "/clients/%s" puts alice in /clients/alice with exactly the listed permissions. Nothing is written to the users file, so there is nothing to clean up — and the visitor cannot reach another customer's folder even if the portal is misconfigured.

create creates a real account on first sign-in, which you can then edit in Admin → Users. Use it when the remote app owns the user list and you want them visible here. The account has a password nobody knows, so it can only be entered through this application, never from the login form.

Building the URL

The signature is HMAC-SHA256 over user|app|exp|nonce, hex encoded.

GET /auth/sso?user=alice&app=panel&exp=1792933275&nonce=<32 hex chars>&sig=<hex>
sig = HMAC-SHA256(secret, "alice|panel|1792933275|cf9683ba…")

The fields are joined with | in that order; nothing is URL-encoded before signing.

PHP helper

Drop this in your application and call it at the moment of redirect.

<?php
/**
 * Builds a signed FileGator single sign-on URL.
 *
 * The URL is single-use and expires in 60 seconds, so build it when you
 * redirect and never cache or log it.
 *
 * @param string $baseUrl  e.g. "https://files.example.com" or ".../browser"
 * @param string $app      application name configured in FileGator
 * @param string $secret   that application's secret
 * @param string $username the user to sign in as
 * @param int    $ttl      seconds the link stays valid
 * @return string
 */
function filegator_sso_url($baseUrl, $app, $secret, $username, $ttl = 60)
{
    $expiry = time() + $ttl;
    $nonce  = bin2hex(random_bytes(16));

    // Must match internal/sso.Signature: HMAC-SHA256 over
    // "user|app|exp|nonce", hex encoded.
    $payload   = $username . '|' . $app . '|' . $expiry . '|' . $nonce;
    $signature = hash_hmac('sha256', $payload, $secret);

    $query = http_build_query([
        'user'  => $username,
        'app'   => $app,
        'exp'   => $expiry,
        'nonce' => $nonce,
        'sig'   => $signature,
    ]);

    return rtrim($baseUrl, '/') . '/auth/sso?' . $query;
}

Use it:

require 'filegator-sso.php';

header('Location: ' . filegator_sso_url(
    'https://files.example.com',    // or '.../browser' when mounted under a path
    'panel',
    getenv('FILEGATOR_SSO_SECRET'),
    $user->login
));
exit;

The signature this produces is pinned by a test, so it cannot drift from the Go implementation.

Any language works — it is one HMAC:

import hmac, hashlib, time, secrets

exp, nonce = int(time.time()) + 60, secrets.token_hex(16)
payload = f"alice|panel|{exp}|{nonce}"
sig = hmac.new(secret.encode(), payload.encode(), hashlib.sha256).hexdigest()
url = f"https://files.example.com/auth/sso?user=alice&app=panel&exp={exp}&nonce={nonce}&sig={sig}"

Rules the implementation enforces

Administrator access

admin_usernames grants administrator only within sessions created by that application. A portal listing jane cannot make anyone an administrator in FileGator itself, and removing the name takes effect the next time she signs in through that portal.

Leave the list empty unless you mean it: an application that can grant administrator can also change storage settings.

What a session created this way can do

The same as any session: the permissions in the policy, confined to the template home. It cannot reach the admin area unless it is an administrator. policy and create sessions are ordinary sessions afterwards — FileGator has no idea how they started, and logging out works normally.

The session does not follow the remote application's session. Logging out of the panel does not log the user out of FileGator; that happens when the session expires or the user logs out here. Lower security.session_ttl if you need tighter coupling.

Troubleshooting

Every refusal is logged as auth.sso_failed with the reason:

{"event":"auth.sso_failed","msg":"single sign-on refused","app":"panel","reason":"expired"}
ReasonCause
expiredThe link was built more than expiry seconds ago — clock skew between servers is the usual culprit
replayedThe link was already used. Build a new one per redirect
unknown usermode: existing and the account does not exist
invalidWrong secret, wrong app name, or a field edited after signing

A successful handoff logs auth.sso_login, naming the application and whether an account was created.