Authentication#
Reading a public book needs no credential at all, but the bookshelf does have some features that require authentication, such as reading private books, drafting a book, and publishing one.
This page covers the methods the SDK and the bookshelf CLI support, which one to pick, and the order they are tried in. The environment variables named here are described in full in Configuration.
The bookshelf credentials may be accepted by other Climate Resource services for authentication and authorisation.
Authentication methods#
Pick the method that matches who is calling.
| Method | Who it is for | How to start |
|---|---|---|
| Anonymous | anyone reading public books | nothing to do |
| User login, browser | a person on a machine with a browser | bookshelf auth login |
| User login, device code | a person on a headless machine or over SSH | bookshelf auth login --no-browser |
| Bearer token | a script handed a token by something else | $BOOKSHELF_TOKEN, or Bookshelf(auth="...") |
| GitHub Actions | a workflow reading its organisation's books | $BOOKSHELF_AUTH=github-actions |
Log in as a person#
bookshelf auth login starts an interactive login and stores the result, so you do this once per machine rather than once per session.
On a machine with a browser it opens one, you sign in, and the credential comes back to the CLI.
On a machine with no browser it uses the device authorization flow instead, which moves the sign-in to a machine that has one. The CLI prints a short code and a URL:
- The CLI prints the code and starts waiting.
- You open that URL on any other machine, on a phone if that is what is to hand.
- You sign in and confirm the code shown there matches the one the CLI printed.
- The CLI notices the approval and stores the credential.
The code expires after a few minutes. Run the command again for a fresh one.
bookshelf auth login always tries a browser first, so pass --no-browser to take this path. In Python, Bookshelf().ensure_authenticated() picks the device flow by itself in a notebook, an SSH session or a terminal with no usable browser.
The client renews the stored credential by itself, so a login lasts across sessions. If the issuer refuses to renew it, the client warns and falls back to anonymous access. Run bookshelf auth login again to replace it.
Use a token directly#
$BOOKSHELF_TOKEN is sent as a bearer token exactly as given. Nothing refreshes it, so a short-lived token will expire part way through a long run. bookshelf auth token prints a current token from a session that is already logged in, which is the usual way to get one.
In Python, auth= on Bookshelf overrides everything else. It takes a bearer token string, any httpx.Auth instance, or None.
auth=None stays unauthenticated even when a stored login exists, which is the way to guarantee a client only ever sees public data.
Read from GitHub Actions#
A workflow job reads its organisation's books with the token GitHub mints for it, so the repository holds no Bookshelf secret. The repository has to be enrolled, which is what the Bookshelf GitHub App installation does.
Give the job the permission and set the opt-in variable:
The opt-in is deliberate. A job that holds id-token: write for something else never sends its token to Bookshelf unless the workflow explicitly opts in.
The token is read-only, and every write is refused, so a job that publishes still needs a credential of its own. Tokens last minutes, so the client mints a fresh one and replaces it whenever the API refuses one.
Credential precedence#
With auth= omitted, the client walks this chain and takes the first step that answers.
$BOOKSHELF_TOKEN- the job's GitHub Actions OIDC token, when
$BOOKSHELF_AUTHisgithub-actions $BOOKSHELF_CLIENT_IDwith$BOOKSHELF_CLIENT_SECRET- the stored login for that deployment
- unauthenticated
Passing auth= skips the chain entirely. Any credentials are scoped to a single bookshelf deployment.
bookshelf auth whoami reports the current identity. In Python, Bookshelf().ensure_authenticated() confirms the API accepts the credential, and attempts to prompt for authentication if you are not logged in.
Manage stored logins#
One machine holds one login per deployment. Pick the deployment with --api-url or $BOOKSHELF_URL.
bookshelf auth listshows every stored login.--jsonemits one object per login.expiredsays the access token has expired, andneeds_loginsays nothing stored can renew it, so only a fresh login brings it back.bookshelf auth tokenprints the current access token and nothing else, refreshing it first when one is due.bookshelf auth logoutclears the stored login.--allcovers every deployment.
Use a Bookshelf login against another service#
Other Climate Resource services verify the same WorkOS tokens, so a library talking to one of them can reuse the identity from bookshelf auth login. bookshelf.auth resolves the credential exactly as a Bookshelf client does.
import httpx
from bookshelf.auth import access_token, default_auth
with httpx.Client(base_url="https://ndc-api.example", auth=default_auth()) as client:
client.get("/v1/me")
headers = {"Authorization": f"Bearer {access_token()}"}
default_auth()returns anhttpx.Auththat refreshes the token and retries once on a 401.- A stored login whose refresh fails carries on unauthenticated with a warning. Pass
strict=Trueto get theAuthenticationErrorinstead. access_token()returns a current bearer string for clients that are not built onhttpx. It raisesAuthenticationErrorwhen a stored login cannot be refreshed.- Both take
api_urlto choose which Bookshelf deployment's stored login to use. - The provider classes (
ClientCredentials,ActionsOidcTokenand the rest) are exported for building a credential by hand.
Whether a token is accepted is up to the receiving service. The GitHub Actions token is minted for the bookshelf-read audience, so pass ActionsOidcToken(audience=...) explicitly for a service that expects its own.
Every name in bookshelf.auth.__all__ is covered by the semver promise.
Where credentials are stored#
Credentials live in credentials.json, in the user config directory that platformdirs picks:
- On Linux it is
$XDG_CONFIG_HOME/bookshelf, which defaults to~/.config/bookshelf. - On macOS it is
~/Library/Application Support/bookshelf. A recent platformdirs honours$XDG_CONFIG_HOMEthere too, so with it set the file is under$XDG_CONFIG_HOME/bookshelfinstead.
bookshelf auth login prints the path it stored to, and bookshelf auth list --json shows what the file holds without reading it yourself. The file is written 0600.
It holds every stored login, one per deployment.
The file format may change in any release, so do not read or write it yourself, instead use CLI helpers such as bookshelf auth token to access the contents.