Skip to content

feat(cli)!: expire tokens by default#21783

Merged
johnstcn merged 19 commits intomainfrom
feat/tokens-expire-command
Feb 17, 2026
Merged

feat(cli)!: expire tokens by default#21783
johnstcn merged 19 commits intomainfrom
feat/tokens-expire-command

Conversation

@johnstcn
Copy link
Member

@johnstcn johnstcn commented Jan 30, 2026

Summary

NOTE: Calling this out as a breaking change in case existing consumers of the CLI depend on being able to see expired tokens OR being able to delete tokens immediately.

Updates the coder tokens rm command to immediately expire a token by ID, preserving the token record for audit trail purposes. Tokens can still be deleted by passing --delete.

Problem

During an incident on dev.coder.com, operators needed to urgently expire an API key that was stuck in a hot loop. The only way to do this was via direct database access:

UPDATE api_keys SET expires_at = NOW() WHERE id = '...';

This is not ideal for operators who may not have direct DB access or want to avoid manual SQL.

Solution

This PR adds:

  • API endpoint: PUT /api/v2/users/{user}/keys/{keyid}/expire - Sets the token's expires_at to now
  • SDK method: ExpireAPIKey(ctx, userID, keyID)
  • Updates CLI: coder tokens rm <name|id|token> now expires by default. You can still delete by passing the --delete flag. The coder tokens list command now also hides expired tokens by default. You can --include-expired if needed to include them.
  • Audit logging: The expire action is logged with old and new key states

Test plan

  • Tests cover: owner expiring own token, admin expiring other user's token, non-admin cannot expire other's token, 404 for non-existent token

Closes #21782

🤖 Generated with Claude Code

This adds a new `coder tokens expire` command that allows admins/owners
to immediately expire a token by ID. Unlike `coder tokens rm` which
deletes the token entirely, this preserves the token record for audit
trail purposes.

This is useful during incident response when a token needs to be
urgently revoked without direct database access.

The implementation includes:
- New API endpoint: PUT /api/v2/users/{user}/keys/{keyid}/expire
- New SDK method: ExpireAPIKey
- New CLI command: coder tokens expire <name|id|token>
- Audit logging for the expire action

Closes #21782

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@johnstcn johnstcn force-pushed the feat/tokens-expire-command branch from 58aa3ec to 42b4846 Compare January 30, 2026 14:20
@johnstcn johnstcn marked this pull request as ready for review February 3, 2026 09:28
@deansheather
Copy link
Member

Why not just use coder tokens remove? If the expiry functionality is what you specifically want for auditing puposes, why not change that endpoint to expire instead of delete?

@coder-tasks

This comment was marked as duplicate.

coder-tasks[bot]

This comment was marked as duplicate.

This comment was marked as duplicate.

@coder-tasks

This comment was marked as duplicate.

@johnstcn
Copy link
Member Author

johnstcn commented Feb 3, 2026

Why not just use coder tokens remove? If the expiry functionality is what you specifically want for auditing puposes, why not change that endpoint to expire instead of delete?

Good point - dbpurge will automatically delete API keys expired for a certain threshold. The only worry I'd have here is that users will complain about seeing expired API keys in the CLI output, which is a temporary state anyway (7 days by default).

@deansheather
Copy link
Member

Could control it with a flag, perhaps by default? IDM, but I don't think we should have two commands that are almost identical in function

@johnstcn
Copy link
Member Author

johnstcn commented Feb 3, 2026

Could control it with a flag, perhaps by default? IDM, but I don't think we should have two commands that are almost identical in function

Something like coder tokens delete --expire=false my-token?

@deansheather
Copy link
Member

If the default behavior of remove becomes expiry, maybe --force
Otherwise, --expire=true

@deansheather deansheather removed their request for review February 4, 2026 13:05
@johnstcn johnstcn marked this pull request as draft February 9, 2026 09:17
@johnstcn johnstcn marked this pull request as ready for review February 16, 2026 09:24
@coder-tasks

This comment was marked as duplicate.

This changes the default behavior of `coder tokens remove` to expire
tokens instead of deleting them, preserving the audit trail. Use
`--delete` to permanently delete a token.

Changes:
- Default behavior now expires tokens (preserves audit trail)
- Add `--delete` flag for permanent deletion
- Add `--include-expired` flag to `tokens ls` to show expired tokens
- Add PUT /users/{user}/keys/{keyid}/expire API endpoint
- Add ExpireAPIKey SDK method

Breaking change: Scripts using `coder tokens remove` will now expire
tokens instead of deleting them. Update to `coder tokens remove --delete`
for the previous behavior.
@johnstcn johnstcn changed the title feat(cli): add coder tokens expire command feat(cli)!: expire tokens by default Feb 16, 2026
@github-actions github-actions bot added the release/breaking This label is applied to PRs to detect breaking changes as part of the release process label Feb 16, 2026
@coder-tasks

This comment was marked as outdated.

### Remove or expire a token

You can remove a token using the CLI or the API. By default, `coder tokens remove`

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

Copy link
Contributor

@dannykopping dannykopping left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍 a few minor comments

cli/tokens.go Outdated
cmd := &serpent.Command{
Use: "remove <name|id|token>",
Aliases: []string{"delete"},
Short: "Delete a token",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The short description is stale.

Lifetime: time.Hour * 24 * 7,
})
require.NoError(t, err)
keyID := strings.Split(res.Key, "-")[0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not something for this PR, but it bugs me that we don't have a parser for our API key format. We should be hiding this implementation detail behind a method on the Key.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

@coder-tasks
Copy link
Contributor

coder-tasks bot commented Feb 17, 2026

Documentation Check

Updates Needed

  • docs/admin/users/sessions-tokens.md - Incorrect statement about expired token visibility

    ⚠️ Lines 108-109 are incorrect: The documentation currently states "Expired tokens can no longer be used for authentication but remain visible in token listings." However, the actual behavior is that expired tokens are hidden by default in coder tokens list unless you pass --include-expired.

    Suggested fix: Change lines 108-109 to:

    Expired tokens can no longer be used for authentication and are hidden by default
    in token listings. Use `coder tokens list --include-expired` to view expired tokens.
    

Previously Completed

  • Section "Remove or expire a token" added explaining the default expire behavior and --delete flag
  • CLI reference auto-generated for tokens_remove.md documenting --delete flag and new default behavior
  • CLI reference auto-generated for tokens_list.md documenting --include-expired flag
  • API reference documents the PUT /users/{user}/keys/{keyid}/expire endpoint
  • Audit logs documentation updated (auto-generated)

Summary

The documentation has been mostly updated, but contains an inaccuracy about expired token visibility that contradicts the actual CLI behavior (expired tokens are hidden by default, not visible).


Automated review via Coder Tasks

Aliases: delete, rm

Remove a token by expiring it. Use --delete to permanently delete the token
instead, which removes the audit trail.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be stale?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep forgot to make gen

@johnstcn johnstcn merged commit 4a3304f into main Feb 17, 2026
29 checks passed
@johnstcn johnstcn deleted the feat/tokens-expire-command branch February 17, 2026 13:16
@github-actions github-actions bot locked and limited conversation to collaborators Feb 17, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release/breaking This label is applied to PRs to detect breaking changes as part of the release process

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cli: add coder tokens expire command

4 participants