Side reading

OAuth 2.0 is an authorisation framework: it gets an application permission to do something. OpenID Connect is a thin layer on top that adds identity: it tells the application who you are. When you configure "sign in with", you are configuring OIDC.

The parameters #

issuer identifies the provider, as a URL. Everything else is discovered from it: fetch <issuer>/.well-known/openid-configuration and you get every endpoint and the location of the signing keys. Get this right and most of the rest is automatic.

client_id identifies the application. Not secret.

client_secret authenticates a confidential client when it talks directly to the provider. Secret. Mobile and other public clients cannot keep one.

redirect_uri is where the provider may send the user back. Register and match it exactly, including scheme, port and trailing slash.

scope is what the application is asking for. openid is required and is what makes it OIDC rather than plain OAuth. profile adds name and username, email adds the address.

state is a random value the application generates and checks on return, which stops an attacker feeding your browser a login they started.

nonce is a random value that ends up inside the token, so the application can confirm the token was minted for this login and not replayed.

code_challenge / PKCE binds the authorisation code to the client that requested it. Use authorisation code plus PKCE for public and confidential clients when both products support it.

The tokens #

ID token. A JWT: three base64url segments separated by dots, holding a header, claims and a signature. The client verifies the signature with the provider's published keys and checks the exact issuer, intended audience/client ID, expiry and nonce. Decoding the segments performs none of those validations.

Access token. A credential for calling APIs on the user's behalf. Often opaque. Not for identifying the user, though plenty of code misuses it that way.

Refresh token. Obtains new access tokens without asking the user again. Long-lived, and therefore the most dangerous of the three to leak.

What the claims mean #

sub is the subject: a stable, unique identifier for the user at this provider. This is the one to key accounts on.

email is not stable. People change addresses, and providers may reuse them.

email_verified says whether the provider checked the address. Applications that match accounts by email should require it, because otherwise someone registering with an address they do not own can take over an existing account.

groups is not standard. Providers put group membership in a claim by convention, and each application needs telling which claim to read.

Matching accounts by email is the common vulnerability

Application sees alice@example.com in a token, finds a local account with that address, logs you in as her.

That is fine when the provider verified the address and you control who can register. It is a takeover when either is untrue. Key on sub where the application lets you; require email_verified where it does not.

Reading an error #

Failures cluster into four kinds, and each has a signature.

redirect_uri_mismatch, or a provider page saying the redirect is not allowed. What is registered differs from what was sent, usually by a trailing slash, http against https, or a port. Compare them character by character.

invalid_client. The client_id or client_secret is wrong, or the secret has been rotated on one side only.

A redirect loop. The application authenticates, then does not believe its own session, and starts again. Usually cookie related: check that the application knows its own external URL, and that it is behind HTTPS if it expects to be. A reverse proxy that does not forward the original scheme causes exactly this.

Logged in, then refused. Authentication worked and authorisation did not. A group binding, a required claim that is missing, or the application's own permissions. Look at the provider's logs: they will show a successful login for a user the application then rejected.

Checking a token by hand #

The provider's endpoints are all listed in one document:

$ curl -s '<auth-service-url>/application/o/immich/.well-known/openid-configuration' \
    | python3 -m json.tool

A JWT can be decoded without any tooling, since the payload is base64. Decoding is not verifying: it tells you what the token claims, not whether the signature is good.

JWTs use base64url without padding, which plain base64 -d refuses, so translate the alphabet and pad it:

$ python3 -c 'import base64,json,sys; s=sys.argv[1]; print(json.dumps(json.loads(base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))), indent=2))' '<the middle segment>'

Use local test tokens only and do not paste them into a public decoder, chat or issue. This inspection can answer whether a claim exists; it cannot establish that the token is authentic or acceptable.

Settings

Your values

The book is written with placeholder names so it makes sense to everybody. Put your own in and every chapter, every command and every copy-paste prompt updates to match.

Nothing here is sent anywhere. It is saved in this browser, so it comes back next time. A different browser or a private window gets the placeholders again.

Live preview

$ ssh admin@192.168.1.20
$ sudo ufw allow from 192.168.1.0/24 to any port 22 proto tcp
$ sudo hostnamectl set-hostname homeserver
$ sudo timedatectl set-timezone Europe/Paris

Real commands from chapters 2, 3 and 4. They change as you type.

The account you log in as. Not root, and not necessarily the same name you use on your laptop.

Introduced in Chapter 2, Meet your server

The book's placeholder is admin

What the machine calls itself. You choose it, and it shows up in your shell prompt and your logs.

Introduced in Chapter 3, A safe front door

The book's placeholder is homeserver

The IP address your server has on your home network, from ip -brief addr.

Introduced in Chapter 2, Meet your server

The book's placeholder is 192.168.1.20

The address range and prefix shown by ip route or ip -brief addr, written in CIDR form. Copy the real prefix; do not guess /24.

Introduced in Chapter 2, Meet your server

The book's placeholder is 192.168.1.0/24

The address traffic goes to on its way out of your house, from ip route.

Introduced in Chapter 2, Meet your server

The book's placeholder is 192.168.1.1

In Region/City form, or Etc/UTC if you would rather read logs in UTC.

Introduced in Chapter 3, A safe front door

The book's placeholder is Europe/Paris

A registered name you control. Chapter 8 uses it for the LAN route; chapter 12 uses a separate private Tailscale name remotely.

Introduced in Chapter 8, One door, many rooms

The book's placeholder is example.com

The email identity allowed to administer the tagged server in your Tailscale policy.

Introduced in Chapter 10, Your own private network

The book's placeholder is you@example.com

The mailbox that should receive actionable home-server alerts.

Introduced in Chapter 14, Knowing it is alive

The book's placeholder is alerts@example.com

Once you save, the prose and the commands read with your names, the copy buttons copy your values, and the copy-paste prompts describe your machine accurately. That last one matters: an assistant told your network is 192.168.1.0/24 when it is not will send you chasing the wrong thing.

Anything you leave empty keeps the book's placeholder.