Olympus Docs
CookbookDefensive security

Just-in-time admin elevation

Reduce standing admin privileges by elevating only when needed

Standing admin access is a security risk: if compromised, attacker has high privileges constantly. Just-in-time (JIT) elevation: be a regular user by default, request admin when needed (with strong auth + audit).

Pattern

1. Regular user. No admin privileges.
2. User clicks "Request admin access" → enters reason.
3. Approval required (auto or another admin).
4. Admin role granted for 1 hour.
5. After 1 hour, expires automatically.

Reduces blast radius: stolen credentials of an "admin user" don't have admin scope unless they've recently elevated.

Data model

CREATE TABLE admin_elevations (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  identity_id UUID NOT NULL,
  reason TEXT NOT NULL,
  requested_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  approved_at TIMESTAMPTZ,
  approved_by UUID,
  expires_at TIMESTAMPTZ NOT NULL,
  revoked_at TIMESTAMPTZ
);

CREATE INDEX ix_active_elevations 
  ON admin_elevations(identity_id, expires_at) 
  WHERE revoked_at IS NULL AND approved_at IS NOT NULL;

Check on each admin endpoint

async function requireAdmin(req) {
  const session = await getSession(req);
  if (session.identity.traits.base_role !== "admin_capable") {
    throw new Forbidden("not_admin_capable");
  }
  
  const active = await db`
    SELECT * FROM admin_elevations
    WHERE identity_id = ${session.identity.id}
      AND approved_at IS NOT NULL
      AND revoked_at IS NULL
      AND expires_at > NOW()
    LIMIT 1
  `.first();
  
  if (!active) {
    throw new StepUpRequired("elevation_required");
  }
}

User has the capability to be admin (configured globally). Currently elevated? Only if there's an active elevation.

Request flow

"use client";
export function RequestElevationButton() {
  const [reason, setReason] = useState("");
  
  async function request() {
    await fetch("/api/admin-elevation", {
      method: "POST",
      body: JSON.stringify({ reason }),
    });
    window.location.reload();
  }
  
  return (
    <Modal>
      <h2>Request admin access</h2>
      <p>You'll have admin privileges for 1 hour.</p>
      <textarea value={reason} onChange={(e) => setReason(e.target.value)} 
                placeholder="Why do you need admin? (logged)" required />
      <Button onClick={request}>Elevate</Button>
    </Modal>
  );
}

Approval modes

Mode A: Self-approval

User clicks → immediate elevation. But:

  • Require MFA at request.
  • Reason logged.
  • Time-limited.

For trusted admins, this is the most ergonomic. The audit trail provides accountability.

Mode B: Peer approval

Another admin must approve:

1. User requests elevation.
2. Slack/email to other admins: "Alice wants admin for 1h. Reason: [X]. Approve?"
3. Another admin clicks approve.
4. Elevation granted.

For higher-stakes systems. Slower but harder to abuse.

Mode C: Manager approval

Specific manager approves their reports' requests. Hierarchy.

MFA on elevation request

Even with self-approval, require MFA:

if (!session.recent_mfa) {
  return forceStepUp();
}

A compromised non-MFA'd session can't elevate.

Expiration

UPDATE admin_elevations 
SET revoked_at = NOW() 
WHERE expires_at < NOW() AND revoked_at IS NULL;

Daily cron, or on each access check. Auto-expire, user must re-request.

Manual revoke

User finishes their work; they can voluntarily end their elevation:

async function endElevation(elevationId: string, identityId: string) {
  await db`
    UPDATE admin_elevations 
    SET revoked_at = NOW() 
    WHERE id = ${elevationId} AND identity_id = ${identityId} AND revoked_at IS NULL
  `;
}

Encourage this, reduces window of risk.

Audit

Every elevation event:

INSERT INTO security_audit (event_type, actor_id, metadata)
VALUES (
  'admin_elevation_requested',
  $admin_id,
  '{"reason": "$reason", "expires_in_minutes": 60}'
);

And every admin action during elevation:

INSERT INTO security_audit (event_type, actor_id, metadata)
VALUES (
  'admin_action',
  $admin_id,
  '{"action": "deleted_user", "target": "$target_id", "elevation_id": "$elev_id"}'
);

Trace: this action happened during this elevation.

Notification

Notify on elevation:

  • Slack channel for security team.
  • Email to all-admins or the user themselves.
Subject: Admin elevation granted

Alice@your-corp.com elevated to admin for 1 hour.
Reason: "Migrating user data"
Expires: 2026-05-13 15:00 UTC

When NOT to use JIT

  • 24/7 ops needs (someone always needs admin to respond to incidents). Pair with on-call rotation; on-call has standing admin.
  • Small teams (1-2 admins) where the friction outweighs benefit.

For larger teams (> 5 admins) or compliance environments, JIT is valuable.

In Athena

Athena's settings UI can manage elevations:

// app/admin/elevations/page.tsx
const elevations = await db`SELECT * FROM admin_elevations ORDER BY requested_at DESC LIMIT 50`;

return (
  <table>
    {elevations.map(e => (
      <tr>
        <td>{e.identity.email}</td>
        <td>{e.reason}</td>
        <td>{e.approved_at ? "active" : "pending"}</td>
        <td>{e.expires_at}</td>
        <td><Button onClick={() => revoke(e.id)}>Revoke</Button></td>
      </tr>
    ))}
  </table>
);

Active elevations visible. Force-revoke if needed.

Standing vs JIT

You CAN combine: some admins (CEO, founder) have standing access (trust). Others have JIT (junior engineers).

function isStandingAdmin(identity) {
  return ["founder", "cto"].includes(identity.traits.role);
}

Tune to your team's needs.

On this page