# Fix Jupyter server token and password login failures

Fix Jupyter login failures: classify stale-token vs rejected-password vs password-written-to-the-wrong-config-file from the symptom, retrieve the current token via jupyter server list, set passwords with jupyter server password (argon2 hash in jupyter_server_config.json), and handle remote/proxy access.

Exact reference: {"kind":"skill_version","skill_id":"skl_VMoTARYccfwEiHI0yChkAw","version_id":"skv_bYoPhAiSM_gCdQiWyQ_ogw"}

Applicability: [{"constraint":">=2.0 (IdentityProvider hashed-password layout); token behavior applies to >=1.0","technology":"jupyter_server","version_scheme":"semver"},{"constraint":"7.x runs on jupyter_server; 6.x uses the legacy NotebookApp password file","technology":"notebook","version_scheme":"semver"}]

# Fix Jupyter server token and password login failures

Use this when the browser shows `Invalid credentials`, loops back to the login
page, or a token URL that used to work stops working. Token auth and password
auth are separate mechanisms configured in separate places — classify which one
you're fighting before changing anything.

## 1. Classify: token problem vs password problem vs ignored config

**A. A token URL fails:** tokens are generated fresh at every server start. A
bookmarked token, or a token from `jupyter server list` output copied before a
restart, is dead. Get the current one (step 2).

**B. The password form rejects you** (`Invalid credentials`, and the server log
shows `401 POST /login`): the server isn't reading the password you think it is
(step 3).

**C. You expected password auth but get a token prompt (or vice versa):** the
password was written to a config file the running server doesn't read. The
classic cause is setting it for the wrong server generation (step 3).

## 2. Token lookup: find the current one

```bash
jupyter server list
```

This lists your running servers with their current token URLs — copy the full
URL including the token query parameter. Two things that routinely confuse:

- The token in the auto-opened browser tab is a **one-time** token; the
  persistent token is the one printed in the terminal at server startup.
- `jupyter server list` only sees servers started by the same user. A token
  from another user's (or root's) server will never work for yours.

## 3. Password setup: set it the supported way, in the file the server reads

```bash
jupyter server password
```

This prompts twice and writes an argon2 hash into `jupyter_server_config.json`
(under an IdentityProvider section holding a hashed-password value that starts
with `argon2:`). The plaintext is never stored. `jupyter server --generate-config`
creates the `~/.jupyter/jupyter_server_config.py` file if you prefer Python config.

Why a password you set still gives `Invalid credentials`:

- **Wrong server generation.** `jupyter notebook password` (Notebook 6 era)
  writes a NotebookApp password into `jupyter_notebook_config.json` — the new
  jupyter_server ignores it entirely. Use `jupyter server password`.
- **Stale config lines.** Old `c.NotebookApp.password` or `c.ServerApp.password`
  lines in config files are ignored by jupyter_server 2.x, which reads the
  hashed password from `jupyter_server_config.json` (the `IdentityProvider`
  section). Remove the stale lines.
- **Wrong config directory.** Check `jupyter --config-dir`; the `JUPYTER_CONFIG_DIR`
  environment variable overrides it (handy in containers — set it to a directory
  you bake the config file into).
- To hash programmatically instead of interactively: `from jupyter_server.auth
  import passwd` and call `passwd()`.

## 4. Remote and proxied servers

- Bind all interfaces: `jupyter lab --ip` with the all-interfaces address (config key `c.ServerApp.ip`).
- Open the URL **with the token** from `jupyter server list` or the terminal;
  opening plain `/lab` just redirects to the login page.
- Behind a reverse proxy with a subpath: a login loop where the POST goes to the
  wrong path is a proxy/base_url mismatch, not a bad password. Set
  `c.ServerApp.base_url` to the subpath the proxy serves.

## 5. Deliberately disabling auth (local-only machines)

In `jupyter_server_config.py`, setting `c.ServerApp.token` to an empty string
disables token authentication. Only do this on a machine you trust that isn't
reachable from the network — never on a shared or network-exposed server.

## 6. Checklist for the login failure

1. Reproduce: note whether it's a token URL, the password form, or a login
   loop; check the server log for `401 POST /login`.
2. Token retrieval: pull the current token URL from `jupyter server list`; discard any
   token from before the last server restart.
3. Password configuration: set it with `jupyter server password`; confirm the server reads
   `jupyter_server_config.json` (check `jupyter --config-dir`); delete stale
   NotebookApp/ServerApp password lines.
4. Remote: `--ip`, full token URL, and `base_url` behind proxies.
5. Never disable auth on a network-exposed server.


## Supporting basis and limitations

Built from the jupyter_server authentication documentation, an authoritative Jupyter Discourse answer by a core developer on jupyter server password (argon2 hashed_password in jupyter_server_config.json), and recurring forum threads on 401 POST /login loops and passwords ignored behind proxies/containers.

## Change and rationale

New skill: fix Jupyter server token and password login failures (classify token vs password vs ignored config).

'Invalid credentials' and token-login loops are constant Jupyter support threads, and the usual failure is a password set for the wrong server generation (notebook vs server config) or a stale token after restart. This skill classifies the symptom first and documents exactly which file each server generation reads.
