Skip to content

feat(oauth): add StatelessOAuthStateStore using JWT#1916

Open
muskaannnr wants to merge 2 commits into
slackapi:mainfrom
muskaannnr:feat/stateless-oauth-state-store
Open

feat(oauth): add StatelessOAuthStateStore using JWT#1916
muskaannnr wants to merge 2 commits into
slackapi:mainfrom
muskaannnr:feat/stateless-oauth-state-store

Conversation

@muskaannnr

@muskaannnr muskaannnr commented Jul 16, 2026

Copy link
Copy Markdown

Implements a stateless OAuth state store that embeds expiry into a signed JWT (HS256/HMAC-SHA256) so no persistent storage is required.
Resolves #1823.

Summary

Adds StatelessOAuthStateStore to slack_sdk.oauth.state_store - a stateless OAuth state store that requires no persistent storage (no files, no database, no S3).

Instead of saving the state somewhere, it encodes the expiry timestamp into a signed JWT (HS256) and returns that as the state string. On consume(), it verifies the HMAC signature and checks the expiry, no I/O needed. Implemented entirely with Python stdlib (hmac, hashlib, base64, json), zero new dependencies.

Trade-off: since there's no storage, consume() is repeatable - the same valid token returns True within its expiry window, unlike FileOAuthStateStore which deletes on first consume. This is an accepted limitation of stateless design, consistent with the Node SDK's ClearStateStore.

Testing

  1. Run the new unit tests: ./scripts/run_tests.sh tests/slack_sdk/oauth/state_store/test_stateless.py
  2. Verify StatelessOAuthStateStore is importable: from slack_sdk.oauth.state_store import StatelessOAuthStateStore
  3. Issue a state and consume it, should return True. Consume with a wrong secret or tampered token, should return False.

Category

  • slack_sdk.web.WebClient (sync/async) (Web API client)
  • slack_sdk.webhook.WebhookClient (sync/async) (Incoming Webhook, response_url sender)
  • slack_sdk.socket_mode (Socket Mode client)
  • slack_sdk.signature (Request Signature Verifier)
  • slack_sdk.oauth (OAuth Flow Utilities)
  • slack_sdk.models (UI component builders)
  • slack_sdk.scim (SCIM API client)
  • slack_sdk.audit_logs (Audit Logs API client)
  • slack_sdk.rtm_v2 (RTM client)
  • /docs (Documents)
  • /tutorial (PythOnBoardingBot tutorial)
  • tests/integration_tests (Automated tests for this library)

Requirements

  • I've read and understood the Contributing Guidelines and have done my best effort to follow them.
  • I've read and agree to the Code of Conduct.
  • I've run python3 -m venv .venv && source .venv/bin/activate && ./scripts/run_validation.sh after making the changes.

Implements a stateless OAuth state store that embeds expiry into a
signed JWT (HS256/HMAC-SHA256) so no persistent storage is required.
Resolves slackapi#1823.
@muskaannnr
muskaannnr requested a review from a team as a code owner July 16, 2026 18:14
Comment thread slack_sdk/oauth/state_store/stateless/__init__.py Outdated
@codecov

codecov Bot commented Jul 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.47368% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.16%. Comparing base (99f4fed) to head (6e647fb).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
slack_sdk/oauth/state_store/stateless/__init__.py 89.28% 6 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1916      +/-   ##
==========================================
+ Coverage   84.15%   84.16%   +0.01%     
==========================================
  Files         117      118       +1     
  Lines       13366    13423      +57     
==========================================
+ Hits        11248    11298      +50     
- Misses       2118     2125       +7     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@vegeris

vegeris commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Thanks for this contribution! The implementation looks good and we've confirmed it works end-to-end with Bolt's OAuth flow.

One thing to consider might be to use compact JSON serialization. Per RFC 7515, JWT serialization should be compact (no spaces). This also produces shorter tokens:

# Current
json.dumps({"alg": "HS256", "typ": "JWT"}).encode()

# Fix
json.dumps({"alg": "HS256", "typ": "JWT"}, separators=(",",
":")).encode()

Same for the payload json.dumps call

@muskaannnr

Copy link
Copy Markdown
Author

Thanks for this contribution! The implementation looks good and we've confirmed it works end-to-end with Bolt's OAuth flow.

One thing to consider might be to use compact JSON serialization. Per RFC 7515, JWT serialization should be compact (no spaces). This also produces shorter tokens:

# Current
json.dumps({"alg": "HS256", "typ": "JWT"}).encode()

# Fix
json.dumps({"alg": "HS256", "typ": "JWT"}, separators=(",",
":")).encode()

Same for the payload json.dumps call

Great point, thanks for the RFC 7515 reference! Added separators=(",", ":") to both json.dumps calls in issue() (header and payload) to produce proper compact JWT serialization.

@muskaannnr
muskaannnr requested a review from vegeris July 17, 2026 19:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stateless OAuth State Store

2 participants