Skip to content

fix(okta): random per-install device token (#23) - #29

Merged
aspratt merged 2 commits into
masterfrom
fix/23-okta-device-token
Oct 20, 2026
Merged

aspratt merged 2 commits into
masterfrom
fix/23-okta-device-token

Conversation

@aspratt

@aspratt aspratt commented Oct 20, 2026 •

Copy link
Copy Markdown
Owner

fix(okta): random per-install device token (closes #23)

Record: #23 (upstream PR Versent#1484).

Problem

The Okta DT (device token) cookie was okta_<username>_saml2aws. Anyone who knows a user's login can derive it, which weakens Okta's "remember this device" MFA bypass.

Change

  • deviceToken() in pkg/provider/okta/okta.go generates a random UUID on first use and stores it in the credential store under <idp url>/deviceToken, the same way the Okta session cookie is stored. Later runs read it back, so the token stays stable for each install and IdP. setDeviceTokenCookie uses it.
  • The username-derived value is never used now, including as a fallback.

After the security review

The maintainer's decisions from the review:

  1. No keychain, or the save fails: the token is kept in a 0600 file under the saml2aws config directory (~/.aws/saml2aws/okta-device-token-<sha256(idp url)[:16]>, the directory the browser provider already uses), and later runs reuse it. If the file can't be read or written, a random token is used for that run only, with a warning.
  2. Rotation: a new keychain token is generated only when credentials.IsErrCredentialsNotFound(err). Any other Get error (for example a locked keychain) leaves the stored token alone. That run uses the file token and logs a warning.
  3. Visible warnings: save failures, unexpected Get errors and file problems are logged at warning level, not debug.

Compatibility

  • Every user gets a new token once, so Okta prompts for MFA one more time before it remembers the device again.
  • Keychain users keep their token in the keychain. Users without a keychain get the file.

Tests

New tests in pkg/provider/okta/okta_devicetoken_test.go. A package TestMain points the token file directory at a temp dir, so no test writes to the real ~/.aws/saml2aws.

  • TestDeviceTokenIsRandomAndStable: the token isn't derived from the username, is saved in the store, is reused, and differs between installs.
  • TestDeviceTokenFileFallbackWithoutCredentialStore: with no store, a random token is written to a 0600 file (the mode isn't checked on Windows) and read back on the next run.
  • TestDeviceTokenTransientGetErrorDoesNotRotate: a Get error other than not-found leaves the stored token unchanged and logs a warning.
  • TestDeviceTokenSaveErrorWarnsAndUsesFile: a failed save logs a warning, never falls back to the predictable token, and the file token is reused on later runs.
  • TestSetDeviceTokenCookie (existing): now asserts the token is not the legacy value.

Evidence:

The Okta device token was predictable (okta_<user>_saml2aws). Each
install now generates a random token and stores it in the keychain.
@aspratt

aspratt commented Oct 20, 2026

Copy link
Copy Markdown
Owner Author

e2e PASS and e2e-aws PASS at 7ef63df (2026-10-20), with 2 known-bug skips tracked in #13 and #14. The Keycloak e2e IdP doesn't exercise the Okta device-token path, so the unit test is the evidence for this change. Review is in progress.

@aspratt

aspratt commented Oct 20, 2026

Copy link
Copy Markdown
Owner Author

Review APPROVE at 7ef63df. Marked ready for approval in #12, with one maintainer decision: when there's no keychain, keep the legacy token (recommended) or use a random token every run, which means MFA on every login. Release note: keychain users get one extra MFA prompt after upgrading.

@aspratt

aspratt commented Oct 20, 2026

Copy link
Copy Markdown
Owner Author

Security second review (Sonnet): CHANGES (Medium). The legacy okta__saml2aws fallback stays predictable on installs with no keychain. The maintainer has now decided: with no keychain, keep a random token in a 0600 file under the saml2aws config directory. Also: warn the user when the save fails, and generate a new token only when none is found, so a transient keychain error doesn't rotate it. Add tests for both paths.

…ent errors (#23)

With no keychain, or when saving to it fails, the random device token is
now kept in a 0600 file under the saml2aws config directory instead of
falling back to the predictable okta_<user>_saml2aws value. A new token
is generated only when none is found, and a failed save is reported to
the user.
@aspratt

aspratt commented Oct 20, 2026

Copy link
Copy Markdown
Owner Author

Review changes made at d2e7c3d. With no keychain, the device token is kept in a 0600 file under the saml2aws config directory, so there's no predictable fallback any more. A new token is generated only on not-found, and a failed save warns the user. Tests added. Sonnet security re-review and e2e are in progress.

@aspratt

aspratt commented Oct 20, 2026

Copy link
Copy Markdown
Owner Author

e2e PASS and e2e-aws PASS at d2e7c3d (2026-10-20). The Sonnet security re-review is in progress.

@aspratt

aspratt commented Oct 20, 2026

Copy link
Copy Markdown
Owner Author

Sonnet security re-review: APPROVE at d2e7c3d. The predictable fallback is gone, the file is created 0600 in a 0700 directory, only not-found rotates the token (mutation-checked), and a failed save warns. Low follow-ups: create the file with O_EXCL|O_NOFOLLOW to close the TOCTOU and symlink window, and tighten an existing loose ~/.aws/saml2aws (same as #33).

@aspratt
aspratt merged commit 9f599f2 into master Oct 20, 2026
7 of 8 checks passed
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.

security: Okta device token is predictable (okta_<user>_saml2aws)

1 participant