Skip to content

Store the tokens and DPoP key in the platform keystore - #53

Merged
gjwgit merged 3 commits into
devfrom
gjw/52_secure_token_store
Sep 13, 2026
Merged

gjwgit merged 3 commits into
devfrom
gjw/52_secure_token_store

Conversation

@gjwgit

@gjwgit gjwgit commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Moves everything solid_auth persists between runs into the platform
keystore. It was going to plaintext shared_preferences.

Problem

OidcDefaultStore falls back to package:shared_preferences for its
secureTokens namespace when no secureStorageInstance is passed, and
solid_auth constructed it bare in three places. That namespace holds the
whole credential set:

  • the access, refresh and ID tokens and the PKCE code_verifier, written
    by package:oidc
  • solid_auth_rsa_private — the PEM-encoded DPoP RSA private key,
    written by SolidAuthSessionStore

The DPoP key is the serious one. It binds an access token to this client
through the cnf.jkt thumbprint, so a plaintext refresh token together
with a plaintext DPoP private key is not merely credential exposure — it
is everything needed to impersonate the client. On desktop that is
readable by any process running as the user, and ordinary
home-directory backups collect the file (RFC 9700 §4.14).

oidc_default_store warned about this on every manager construction.

Fix

All three constructions now go through one createSolidTokenStore() in a
new lib/src/auth/solid_token_store.dart, using oidc_default_store's
own hardened per-platform options rather than hand-rolled ones.

One factory rather than three call sites matters here beyond tidiness:
the warning is emitted per store construction, so fixing only some sites
silences the symptom while leaving the exposure. grep -rn "OidcDefaultStore()" lib/ now returns nothing, so there is no route back
to the unencrypted fallback.

flutter_secure_storage moves from transitive to declared, at the
^10.3.1 solidpod already uses, so no new version enters the tree. Two
stale doc comments are corrected: the factory's "Platform-appropriate
storage", and the session store's claim that the tokens were already
"stored in the same underlying secure storage".

Testing

Verified with todopod on Linux desktop via a path override:

  • the no FlutterSecureStorage warning went from 4+ per run to 0
  • a fresh login wrote the session to the keystore with no
    PlatformException, so the libsecret/GNOME keyring path works
  • a restart read it back and restored the session, so the round trip is
    confirmed rather than just the write

Not yet tested on Android, iOS, macOS, Windows or web. macOS will need
the Keychain Sharing entitlement for flutter_secure_storage to work at
all.

Migration — needs a release note

This changes where solid_auth reads, so a session saved by an older
build is invisible to this one
: it reports No stored session found
and the user logs in once after upgrading. I hit this during testing, so
it is confirmed behaviour, not a theoretical risk. The old plaintext keys
also remain on disk afterwards until something removes them — cleaning
them up would be worth a follow-up, since otherwise the exposed tokens
simply sit there until they expire.

Closes #52

@gjwgit

gjwgit commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

Tested extensively with todopod, notepod, diarypod, innerpod, ++

@gjwgit
gjwgit merged commit bcf0116 into dev Sep 13, 2026
20 checks passed
@gjwgit
gjwgit deleted the gjw/52_secure_token_store branch September 13, 2026 19:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OIDC tokens stored unencrypted: OidcDefaultStore created without a FlutterSecureStorage instance

1 participant