fastapi-startkit-auth 0.6.2__tar.gz → 0.6.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. fastapi_startkit_auth-0.6.3/CHANGELOG.md +296 -0
  2. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/PKG-INFO +107 -60
  3. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/README.md +105 -58
  4. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/pyproject.toml +2 -2
  5. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/__init__.py +60 -14
  6. fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/apitokens/facade.py +63 -0
  7. fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/clients/credentials.py +46 -0
  8. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/clients/models.py +12 -0
  9. fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/clients/orm.py +91 -0
  10. fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/clients/repository.py +61 -0
  11. fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/commands/oauth2_client.py +67 -0
  12. fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/config/__init__.py +33 -0
  13. fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/config/auth.py +223 -0
  14. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/dependencies.py +38 -23
  15. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/exceptions.py +28 -0
  16. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/facade.py +51 -34
  17. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/grants/authorization_code.py +77 -11
  18. fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/grants/client_credentials.py +54 -0
  19. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/grants/password.py +32 -3
  20. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/grants/refresh.py +27 -5
  21. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/guards/guard.py +11 -5
  22. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/guards/session.py +30 -13
  23. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/guards/token.py +26 -5
  24. fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/manager.py +571 -0
  25. fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/middleware/context.py +69 -0
  26. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/middleware/csrf.py +39 -7
  27. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/middleware/session.py +9 -1
  28. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/orm.py +19 -0
  29. fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/policy.py +83 -0
  30. fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/provider.py +221 -0
  31. fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/publishable/migrations/2026_10_02_000001_add_resource_to_oauth_tables.py +15 -0
  32. fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/publishable/migrations/2026_10_04_000001_create_oauth_clients_table.py +18 -0
  33. fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/publishable/migrations/2026_10_04_000002_add_family_id_to_oauth_refresh_tokens_table.py +16 -0
  34. fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/publishable/migrations/2026_10_05_000001_add_scopes_to_oauth_clients_table.py +11 -0
  35. fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/py.typed +0 -0
  36. fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/request_context.py +27 -0
  37. fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/routes.py +497 -0
  38. fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/routes_password.py +53 -0
  39. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/routes_spa.py +1 -12
  40. fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/scoped_method.py +20 -0
  41. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/security/jwt.py +19 -3
  42. fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/sessions/facade.py +48 -0
  43. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/sessions/orm.py +6 -12
  44. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/sessions/state.py +3 -0
  45. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/sessions/store.py +21 -11
  46. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/tokens/models.py +9 -0
  47. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/tokens/orm.py +70 -16
  48. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/tokens/repository.py +47 -8
  49. fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/tokens/service.py +469 -0
  50. fastapi_startkit_auth-0.6.3/tests/conftest.py +303 -0
  51. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/tests/test_api_tokens.py +11 -18
  52. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/tests/test_async_auth.py +32 -27
  53. fastapi_startkit_auth-0.6.3/tests/test_async_refresh_race.py +77 -0
  54. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/tests/test_async_safety.py +46 -32
  55. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/tests/test_async_stores.py +26 -0
  56. fastapi_startkit_auth-0.6.3/tests/test_class_config.py +128 -0
  57. fastapi_startkit_auth-0.6.3/tests/test_client_scopes.py +325 -0
  58. fastapi_startkit_auth-0.6.3/tests/test_clients.py +58 -0
  59. fastapi_startkit_auth-0.6.3/tests/test_facade_api.py +174 -0
  60. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/tests/test_grants.py +46 -11
  61. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/tests/test_guard_registry.py +13 -8
  62. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/tests/test_integration.py +12 -14
  63. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/tests/test_jwt.py +22 -0
  64. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/tests/test_manager.py +6 -7
  65. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/tests/test_model_provider.py +3 -3
  66. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/tests/test_multi_provider.py +25 -18
  67. fastapi_startkit_auth-0.6.3/tests/test_oauth21.py +549 -0
  68. fastapi_startkit_auth-0.6.3/tests/test_oauth2_client_command.py +146 -0
  69. fastapi_startkit_auth-0.6.3/tests/test_oauth_hardening.py +406 -0
  70. fastapi_startkit_auth-0.6.3/tests/test_resource_binding.py +500 -0
  71. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/tests/test_security_fixes.py +8 -9
  72. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/tests/test_session_auth.py +22 -22
  73. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/tests/test_spa_csrf.py +97 -46
  74. fastapi_startkit_auth-0.6.3/tests/test_startkit_provider.py +193 -0
  75. fastapi_startkit_auth-0.6.3/tests/test_store_names.py +49 -0
  76. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/tests/test_sync_regression.py +38 -13
  77. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/tests/test_token_service.py +7 -3
  78. fastapi_startkit_auth-0.6.2/CHANGELOG.md +0 -147
  79. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/application.py +0 -38
  80. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/clients/repository.py +0 -71
  81. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/config.py +0 -109
  82. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/grants/client_credentials.py +0 -30
  83. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/manager.py +0 -406
  84. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/provider.py +0 -85
  85. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/routes.py +0 -368
  86. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/startkit.py +0 -41
  87. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/tokens/service.py +0 -316
  88. fastapi_startkit_auth-0.6.2/tests/conftest.py +0 -166
  89. fastapi_startkit_auth-0.6.2/tests/test_clients.py +0 -58
  90. fastapi_startkit_auth-0.6.2/tests/test_startkit_provider.py +0 -131
  91. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/LICENSE +0 -0
  92. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/apitokens/__init__.py +0 -0
  93. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/apitokens/manager.py +0 -0
  94. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/apitokens/models.py +0 -0
  95. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/apitokens/orm.py +0 -0
  96. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/apitokens/repository.py +0 -0
  97. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/clients/__init__.py +0 -0
  98. /fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/py.typed → /fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/commands/__init__.py +0 -0
  99. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/concurrency.py +0 -0
  100. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/grants/__init__.py +0 -0
  101. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/grants/pkce.py +0 -0
  102. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/guards/__init__.py +0 -0
  103. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/middleware/__init__.py +0 -0
  104. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/passwords/__init__.py +0 -0
  105. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/passwords/broker.py +0 -0
  106. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/passwords/repository.py +0 -0
  107. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/providers/__init__.py +0 -0
  108. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/providers/base.py +0 -0
  109. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/providers/fields.py +0 -0
  110. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/providers/memory.py +0 -0
  111. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/providers/model.py +0 -0
  112. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/publishable/cors.py +0 -0
  113. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000001_create_sessions_table.py +0 -0
  114. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000002_create_personal_api_tokens_table.py +0 -0
  115. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000003_create_oauth_access_tokens_table.py +0 -0
  116. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000004_create_oauth_refresh_tokens_table.py +0 -0
  117. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000005_create_oauth_auth_codes_table.py +0 -0
  118. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/security/__init__.py +0 -0
  119. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/security/hashing.py +0 -0
  120. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/sessions/__init__.py +0 -0
  121. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/sessions/models.py +0 -0
  122. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/src/fastapi_startkit_auth/tokens/__init__.py +0 -0
  123. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/tests/test_hashing.py +0 -0
  124. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/tests/test_model_provider_postgres.py +0 -0
  125. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/tests/test_password_reset.py +0 -0
  126. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/tests/test_reset_throttle_enumeration.py +0 -0
  127. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/tests/test_session_store.py +0 -0
  128. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/tests/test_token_repository.py +0 -0
  129. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.6.3}/tests/test_user_provider.py +0 -0
@@ -0,0 +1,296 @@
1
+ # Changelog
2
+
3
+ All notable changes to `fastapi-startkit-auth` are documented in this file.
4
+
5
+ Release notes are also published on
6
+ [GitHub Releases](https://github.com/fastapi-startkit/fastapi-startkit-auth/releases).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Breaking changes and upgrade notes
11
+
12
+ Each item lists what changed and what to do.
13
+
14
+ - **Feature providers replace `Application` and `AuthServiceProvider`.**
15
+ Register `AuthProvider` first, then the features your guards use:
16
+ `AuthSessionProvider(SessionConfig)`, `AuthOAuth2Provider(OAuth2Config)` and
17
+ `AuthApiTokenProvider(ApiTokenConfig)`. Standalone apps call
18
+ `Provider(config).register(app)`; Startkit apps list the providers in the
19
+ application. *Migrate:* replace `Application([(AuthProvider, Config)])` with a
20
+ `FastAPI()` app plus the providers, and `AuthServiceProvider` with
21
+ `AuthProvider` + the feature providers.
22
+ - **Feature settings moved off `AuthConfig`.** The `session`, `api_tokens` and
23
+ `spa` dicts are rejected at startup; use `SessionConfig`, `ApiTokenConfig`
24
+ (`stateful_origins` turns on SPA mode) instead. *Migrate:* move each dict's
25
+ keys onto the matching dataclass and pass it to its provider.
26
+ - **OAuth settings moved to `OAuth2Config`.** `key`, `algorithm`, the `*_ttl`
27
+ values, `tokens`, `issuer`, `resources`, `scopes`, `pkce_methods` and
28
+ `require_pkce` now live on `OAuth2Config`. Setting them on `AuthConfig` still
29
+ works but emits a `DeprecationWarning`, and an explicit `OAuth2Config` value
30
+ wins. *Migrate:* move them to `OAuth2Config`; the fallback will be removed in a
31
+ future release.
32
+ - **Password grant is opt-in.** `OAuth2Config.grant_types` defaults to
33
+ `["authorization_code", "client_credentials", "refresh_token"]`; `/oauth/token`
34
+ with `grant_type=password` and `/token` answer `unsupported_grant_type`.
35
+ *Migrate:* add `"password"` to `grant_types` to keep it.
36
+ - **Client-less refresh tokens need the password grant.** Refresh tokens are now
37
+ bound to the issuing client: refreshing or revoking one requires that client's
38
+ authentication. Tokens without a client (issued by the password grant) are only
39
+ accepted while `"password"` is enabled, and client credentials sent alongside
40
+ one are authenticated (`invalid_client` if wrong). *Migrate:* send client credentials on
41
+ refresh; keep `"password"` enabled while old client-less tokens are in use.
42
+ - **Consent is mandatory.** `POST /oauth/authorize` returns `access_denied`
43
+ unless the body has `"approved": true`, and always returns `iss` (body and
44
+ redirect, RFC 9207). `response_type` other than `code` fails with
45
+ `unsupported_response_type`. *Migrate:* send `approved: true` after the user
46
+ consents; `GET /oauth/authorize` validates a request for your consent screen.
47
+ - **PKCE no longer defaults to S256.** A missing `code_challenge_method` means
48
+ `plain`, which the default `pkce_methods=["S256"]` refuses. RFC 7636 formats are
49
+ enforced: an S256 challenge is 43 base64url characters, a verifier 43–128
50
+ unreserved characters. *Migrate:* always send `code_challenge_method=S256` and
51
+ a verifier of at least 43 characters.
52
+ - **`client_credentials` is for confidential clients only**; public, revoked or
53
+ grant-disallowed clients get `unauthorized_client` / `invalid_client`.
54
+ Confidential clients must authenticate on the code exchange. *Migrate:* give
55
+ machine clients a secret and list the grant (or leave `grant_types` empty).
56
+ - **Introspection requires a confidential client.** *Migrate:* call
57
+ `/oauth/introspect` with a confidential client's credentials.
58
+ - **`/oauth/clients` HTTP routes are removed.** *Migrate:* create clients with
59
+ `auth:oauth2:client` (Startkit) or `manager.client_repository.register(...)`.
60
+ - **Password reset routes** move to their own router, mounted by `AuthProvider`
61
+ whenever `AuthConfig.passwords` is set (no OAuth needed). Paths are unchanged.
62
+ - **Default guard changed** from `"api"` to `"web"` and `AuthConfig.guards`
63
+ defaults to empty. *Migrate:* set `default` and `guards` explicitly.
64
+ - **CSRF is always on with sessions**, and also accepts the `_token` form field.
65
+ *Migrate:* send `X-XSRF-TOKEN` (or `_token`) on unsafe requests, or list paths
66
+ in `SessionConfig.csrf_exempt_paths`.
67
+ - **OAuth client store defaults to `"database"`** (the ORM `oauth_clients`
68
+ table). *Migrate:* publish and run the migrations, or set
69
+ `OAuth2Config(clients=OAuthClientsConfig(store="memory"))`.
70
+ - **The in-memory OAuth client store is async.** `InMemoryClientRepository`
71
+ methods (`register`, `add`, `find`, `all`, `authenticate`, `revoke`,
72
+ `delete`) are coroutines, like `OrmClientRepository`, and `store="memory"` now
73
+ makes the manager build the async refresh and authorization-code grants. A
74
+ synchronous custom store passed with `store="instance"` keeps the sync grants.
75
+ *Migrate:*
76
+ `await` client-store calls and the `handle` / `issue_code` of those grants.
77
+ - **Persistent stores are named `"database"`.** `store="orm"` still works for
78
+ sessions, API tokens, OAuth tokens and OAuth clients, but emits a
79
+ `DeprecationWarning` and will be removed in a future release. *Migrate:*
80
+ replace `store="orm"` with `store="database"`.
81
+ - **Custom token repositories** must implement `consume_refresh_token` and
82
+ `revoke_token_chain`, and `store_refresh_token` takes `family_id`.
83
+ - Error responses carry `Cache-Control: no-store`; `invalid_client` challenges
84
+ with `WWW-Authenticate: Basic`.
85
+
86
+ ### Migrations
87
+
88
+ All additive; the v0.6.x migrations are unchanged. Publish and run them when
89
+ using the ORM stores:
90
+
91
+ - `2026_10_04_000001_create_oauth_clients_table` — the `oauth_clients` table.
92
+ - `2026_10_04_000002_add_family_id_to_oauth_refresh_tokens_table` — a nullable,
93
+ indexed `family_id` column on `oauth_refresh_tokens`.
94
+ - `2026_10_05_000001_add_scopes_to_oauth_clients_table` — a nullable `scopes`
95
+ column on `oauth_clients`; existing rows (NULL) stay unrestricted.
96
+
97
+ ### Added
98
+
99
+ - Refresh token families with reuse detection: replaying a rotated refresh token
100
+ revokes the whole family, whoever presents it: the owning client, another
101
+ authenticated client, or no client at all for unbound password-grant tokens.
102
+ Refresh tokens are introspectable.
103
+ - `authentication.md`: the full authentication guide.
104
+ - `POST /oauth/revoke` (RFC 7009) revokes the token and its access/refresh chain;
105
+ `GET`/`DELETE /oauth/tokens` and `DELETE /oauth/tokens/{jti}` let a user list
106
+ and revoke their OAuth tokens. `DELETE /oauth/personal-access-tokens` revokes
107
+ all personal access tokens, which also accept a `ttl`.
108
+ - `/.well-known/oauth-authorization-server` metadata (RFC 8414).
109
+ - `OAuth2Config.default_scopes`, `grant_types`, `require_redirect_uri` and
110
+ `authorization_guard`.
111
+ - `OrmClientRepository` and the `auth:oauth2:client` command (`--public`,
112
+ `--name`, repeatable `--redirect-uri`).
113
+ - Per-client scope restrictions: `Client.scopes` lists the scopes a client may
114
+ request (empty means any). A request outside the list, including
115
+ `default_scopes` applied to a request naming none, fails with `invalid_scope`
116
+ on `/oauth/authorize`, the `client_credentials` grant and the password grant
117
+ when a client authenticates. `"*"` is not expanded when matching the allow-list, but a token granted `"*"`
118
+ passes every `require_scopes` check, so never give a client `"*"` (the command
119
+ refuses it).
120
+ Set it with `auth:oauth2:client --scopes`; client repositories gain `revoke`.
121
+ - `AuthMiddleware` and request-scoped facades: `Auth.user()`, `Session.token()`,
122
+ `ApiToken.create(...)` work on the class inside a request. `Auth.validate`
123
+ checks credentials without logging in.
124
+ - `auth` dependency; token guards fall back to the SPA session cookie.
125
+ - Guest sessions are persisted lazily, only when the response is sent.
126
+
127
+ ### Added (resource indicators, #27)
128
+
129
+ - RFC 8707 resource indicators: `OAuth2Config.resources` lists the resources tokens
130
+ may be bound to. The token endpoint and `/oauth/authorize` accept `resource`;
131
+ the code and refresh token remember it and the access token carries it as `aud`.
132
+ Unknown or mismatched resources fail with `invalid_target`.
133
+ - Guards accept an `audience`: a passport guard with `"audience": "<uri>"` only
134
+ accepts tokens bound to that resource, and resource-bound tokens are refused by
135
+ guards without it.
136
+ - `OAuth2Config.issuer` stamps and verifies the `iss` claim.
137
+ - `OAuth2Config.scopes` is a scope catalog: when set, grants refuse unknown scopes
138
+ with `invalid_scope`.
139
+ - `OAuth2Config.pkce_methods` restricts `code_challenge_method` (e.g. `["S256"]`),
140
+ compared case-insensitively; an unknown `code_challenge_method` is now refused
141
+ at `/oauth/authorize` with `invalid_request`.
142
+ - `OAuth2Config.require_pkce` demands a `code_challenge` from confidential clients
143
+ too (OAuth 2.1 / MCP).
144
+ - A repeated `resource` parameter on the token endpoint fails with `invalid_target`.
145
+ - Building a guard whose `audience` is not listed in `OAuth2Config.resources` emits
146
+ a `UserWarning`.
147
+ - `GrantPolicy`, `InvalidScope` and `InvalidTarget` are exported; introspection
148
+ reports `aud` and `iss` when present.
149
+ - Migration `add_resource_to_oauth_tables` adds a nullable `resource` column to
150
+ `oauth_auth_codes` and `oauth_refresh_tokens`; publish and run it when using the
151
+ ORM store.
152
+
153
+ ### Changed
154
+
155
+ - **Breaking:** persistence now goes through the fastapi-startkit ORM with models
156
+ shipped in `fastapi_startkit_auth.orm`; no raw SQL remains. Use
157
+ `{"store": "orm"}` (optional ORM `connection` name) for sessions, API tokens and
158
+ OAuth tokens.
159
+ - Requires `pyjwt>=2.10.1`.
160
+ - **Security:** refresh tokens are bound to the client they were issued to
161
+ (RFC 6749 §6). `RefreshTokenGrant.handle` takes `client_id` and fails with
162
+ `invalid_grant` when it differs from the token's client; the token endpoint
163
+ passes the authenticated client. Callers refreshing a client-issued token must
164
+ now send that client's credentials.
165
+ - **Breaking:** the `startkit` extra now requires `fastapi-startkit[database]>=0.60`.
166
+ - Run the published migrations (`provider:publish -p auth`, then `migrate`) to
167
+ create the tables; stores no longer create tables themselves.
168
+
169
+ ### Removed
170
+
171
+ - **Breaking:** the `sql` / `async_sql` stores and the `SqlSessionStore`,
172
+ `SqlApiTokenRepository`, `AsyncSql*` classes and database adapters.
173
+
174
+ ### Added
175
+
176
+ - `Client.provider` (and `register(provider=...)`) binds an OAuth client to a
177
+ user provider. `AuthManager.owner_provider(client_id)` resolves it, falling
178
+ back to the default guard's provider.
179
+ - `AuthManager.warm_up()` and `AsyncModelUserProvider.warm_up()`, run from the
180
+ app lifespan by `AuthProvider`, precompute the dummy hash at startup.
181
+
182
+ ### Fixed
183
+
184
+ - Refresh, authorization-code exchange and introspection re-check the token
185
+ owner against the provider of the issuing client instead of always using the
186
+ default guard's provider. The password grant authenticates against the
187
+ client's provider, and `/oauth/authorize` rejects a client bound to another
188
+ provider.
189
+ - `AsyncModelUserProvider.is_active` only uses the threadpool for a sync
190
+ callable hook. Attribute checks and `async def` hooks run inline.
191
+ - The first unknown-user login no longer pays an extra bcrypt hash to create
192
+ the dummy hash.
193
+
194
+ ## [0.4.0]
195
+
196
+ ### Added
197
+
198
+ - Async support for async ORMs and drivers, side by side with the sync API:
199
+ - `AsyncModelUserProvider` (provider driver `"async_model"`) for models whose
200
+ `find` / `where(...).first()` / `save` are coroutines; bcrypt runs in a
201
+ worker thread.
202
+ - `AsyncPassportGuard`, `AsyncSessionGuard`, `AsyncTokenGuard`,
203
+ `AsyncTokenService`, `AsyncApiTokenManager`, `AsyncPasswordBroker`, the
204
+ `Async*Grant` variants and the `AsyncAuth` facade.
205
+ - `AuthManager` picks the async variant automatically when any provider
206
+ method or store method is async; sync configurations keep getting the sync
207
+ classes. Sync collaborators used from the async classes run in the
208
+ threadpool.
209
+ - `"async_sql"` stores for sessions (`AsyncSqlSessionStore`), personal API
210
+ tokens (`AsyncSqlApiTokenRepository`) and a new `AuthConfig.tokens` block
211
+ for OAuth access/refresh tokens and authorization codes
212
+ (`AsyncSqlTokenRepository`). They accept an asyncpg pool/connection, an
213
+ aiosqlite connection, or a zero-arg (async) factory. Supported backends are
214
+ PostgreSQL and SQLite. Refresh tokens and authorization codes are stored as
215
+ SHA-256 hashes; refresh revocation and code redemption are atomic and use
216
+ only portable SQL. Expired rows are purged via `expires_at` indexes.
217
+ - `fastapi-startkit` migrations for the five tables, published to
218
+ `databases/migrations/` by `AuthServiceProvider`.
219
+ - `password_key` provider option: the credentials key holding the plaintext
220
+ password, separate from the model column `password_field` (e.g.
221
+ `password_key="password"`, `password_field="hashed_password"`). Defaults to
222
+ `password_field`.
223
+ - `is_active` provider option (attribute name or callable; `async def` with
224
+ the `async_model` driver). Inactive or deleted users are rejected by the
225
+ password grant, the refresh and authorization-code grants, `Auth.attempt` /
226
+ `AsyncAuth.attempt` and every guard, and introspect as inactive.
227
+ - `AsyncMisconfiguration`: raised when a sync guard, grant, broker or `Auth`
228
+ receives an awaitable from a provider or store, so a half-async provider
229
+ fails closed. Sync providers reject an `async def` `is_active` hook.
230
+ - CI runs the asyncpg store and flow tests against a Postgres service.
231
+ - `Typing :: Typed` classifier (the package already ships `py.typed`).
232
+
233
+ ### Changed
234
+
235
+ - **Breaking (signature):** `current_context` and `optional_user` are now
236
+ `async def` dependencies. `Depends(current_user)` and friends keep working
237
+ unchanged; code calling them directly as plain functions must now await
238
+ them. Sync guards still run in the threadpool.
239
+ - The built-in OAuth, password-reset and SPA csrf-cookie routes await async
240
+ collaborators; `SessionMiddleware` accepts an async session store.
241
+ - `Auth` (sync facade) raises `RuntimeError` for async session guards; use
242
+ `AsyncAuth` there.
243
+ - Minimum supported Python is now 3.10. 0.3.0 advertised `>=3.9` but fails on
244
+ 3.9 because runtime-evaluated annotations use `X | None` syntax.
245
+ - The `startkit` extra is bounded to `fastapi-startkit>=0.51,<1.0`.
246
+ - The package `__init__` imports its public API directly instead of through a
247
+ lazy export table.
248
+ - The source distribution now ships the test suite and this changelog.
249
+ - `bin/release.sh` uploads to PyPI with `twine upload`, matching
250
+ fastapi-startkit; the tag-triggered trusted-publishing workflow is removed.
251
+
252
+ ## [0.3.0]
253
+
254
+ First release published to PyPI. Contains the 0.2.0 feature set, built with
255
+ the `uv_build` backend; no functional changes.
256
+
257
+ ## [0.2.0]
258
+
259
+ Adds three new authentication modes alongside the existing OAuth2/JWT server,
260
+ so the package now covers the full Laravel-style auth surface.
261
+
262
+ ### Added
263
+
264
+ - Guard-driver registry in `AuthManager`: guards resolve their driver
265
+ (`token`, `session`, …) from a pluggable registry, enabling multiple guard
266
+ types in one application (Phase 0).
267
+ - Cookie/session authentication: `Auth` facade, `SessionGuard`, session
268
+ middleware, and in-memory + SQL session stores (Phase 1).
269
+ - SPA authentication with CSRF protection: `/__auth__/csrf-cookie` endpoint,
270
+ double-submit-cookie middleware, and an `AuthServiceProvider` that publishes
271
+ the `config/cors.py` stub via `provider:publish -p auth` (Phase 2).
272
+ - Sanctum-style API tokens: `TokenGuard`, opaque hashed tokens with abilities,
273
+ and in-memory + SQL token stores (Phase 3).
274
+
275
+ ### Security / Hardening
276
+
277
+ - Session guard verifies against a constant-time dummy hash when no user is
278
+ found, closing a timing side-channel.
279
+ - SQL session and API-token stores prune expired rows; purge cadence is
280
+ configurable.
281
+ - CSRF cookie endpoint hardened; token repr for `NewApiToken` redacts the
282
+ plaintext value so it cannot leak into logs.
283
+ - DB-API connection vs. connection-factory detection now probes `execute()`
284
+ instead of `callable()`, fixing misdetection of raw `sqlite3.Connection`
285
+ objects in the SQL session and API-token stores.
286
+
287
+ ## [0.1.0]
288
+
289
+ Initial release.
290
+
291
+ - Passport-style OAuth2 authorization server for FastAPI: authorization code
292
+ (with PKCE), password, client credentials, and refresh token grants
293
+ - JWT access tokens with scopes, token guards, and route dependencies
294
+ - Personal access tokens
295
+ - Pluggable user/client providers (in-memory and Masonite ORM)
296
+ - Password reset flow with throttling hardened against account enumeration
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-startkit-auth
3
- Version: 0.6.2
3
+ Version: 0.6.3
4
4
  Summary: Laravel-style authentication for FastAPI: OAuth2/JWT authorization server, cookie/session guard, SPA + CSRF protection, and Sanctum-style API tokens.
5
5
  Keywords: fastapi,oauth2,jwt,auth,passport,sanctum,authentication,authorization,security,tokens,session,cookie,csrf,spa,starter-kit
6
6
  Author: Bedram Tamang
@@ -21,7 +21,7 @@ Classifier: Topic :: Security
21
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
22
  Classifier: Typing :: Typed
23
23
  Requires-Dist: fastapi>=0.110
24
- Requires-Dist: pyjwt>=2.8
24
+ Requires-Dist: pyjwt>=2.10.1
25
25
  Requires-Dist: bcrypt>=4.1
26
26
  Requires-Dist: pydantic>=2
27
27
  Requires-Dist: python-multipart>=0.0.9
@@ -51,17 +51,20 @@ to FastAPI: a config-driven guard/provider/passwords model layered on top of
51
51
  OAuth2 grants and signed JWT access tokens (per the
52
52
  [FastAPI security tutorial](https://fastapi.tiangolo.com/tutorial/security/oauth2-jwt/)).
53
53
 
54
+ The full guide — providers, sessions, API tokens, the OAuth 2.1 server, stores
55
+ and migrations — is in [authentication.md](authentication.md).
56
+
54
57
  ## Features
55
58
 
56
59
  | Area | What you get |
57
60
  | --- | --- |
58
- | **Config layer** | `AuthConfig` (`default` / `guards` / `providers` / `passwords`) + `AuthProvider` that registers into the app |
59
- | **Password grant** | OAuth2 password grant → signed JWT access tokens with expiry (`/oauth/token`, `/token`) |
60
- | **Refresh tokens** | Opaque refresh tokens with **rotation** and configurable TTL |
61
+ | **Config layer** | `AuthConfig` (`default` / `guards` / `providers` / `passwords`) + `AuthProvider`, with `SessionConfig` / `OAuth2Config` / `ApiTokenConfig` feature providers |
62
+ | **Password grant** | Opt-in legacy password grant → signed JWT access tokens (`/oauth/token`, `/token`) |
63
+ | **Refresh tokens** | Client-bound opaque refresh tokens with **rotation** and reuse detection |
61
64
  | **Personal access tokens** | Named, long-lived tokens with scopes/abilities |
62
65
  | **Client credentials** | Machine-to-machine grant |
63
- | **Authorization code + PKCE** | Browser/SPA flow with S256 & plain PKCE |
64
- | **Clients** | Register / list / delete confidential & public clients |
66
+ | **Authorization code + PKCE** | OAuth 2.1 flow with S256 PKCE, explicit consent and `iss` (RFC 9207) |
67
+ | **Clients** | ORM client store and the `auth:oauth2:client` command |
65
68
  | **Revocation & introspection** | RFC 7009 revoke + RFC 7662 introspect |
66
69
  | **Password reset** | `password_reset_tokens` flow with configurable `expire` + `throttle` |
67
70
  | **Guards & deps** | `current_user`, `optional_user`, `require_scopes` FastAPI dependencies |
@@ -81,7 +84,7 @@ Optional extras:
81
84
 
82
85
  | Extra | Installs | Use when |
83
86
  | --- | --- | --- |
84
- | `startkit` | `fastapi-startkit[database]>=0.60,<1.0` (Python 3.12+) | Registering `AuthServiceProvider`, publishing its config stub and migrations via `provider:publish -p auth`, and the `"orm"` stores |
87
+ | `startkit` | `fastapi-startkit[database]>=0.60,<1.0` (Python 3.12+) | Registering the auth providers in a Startkit app, publishing migrations via `provider:publish`, and the `"database"` stores |
85
88
  | `masoniteorm` | `masonite-orm` | Using the `masoniteorm` user provider driver |
86
89
 
87
90
  ```bash
@@ -98,16 +101,16 @@ pins `cleo<2` while `fastapi-startkit` requires `cleo>=2.1`.
98
101
  ## Quickstart
99
102
 
100
103
  ```python
101
- from fastapi import Depends
102
- from fastapi_startkit_auth import Application, AuthProvider, AuthConfig, current_user, require_scopes
104
+ from fastapi import Depends, FastAPI
105
+ from fastapi_startkit_auth import (
106
+ AuthConfig, AuthOAuth2Provider, AuthProvider, OAuth2Config, current_user, require_scopes,
107
+ )
103
108
  from myapp.models import User # any active-record-style model
104
109
 
105
110
 
106
111
  class Config(AuthConfig):
107
- key = "change-me-to-a-long-random-secret" # JWT signing key
108
-
109
112
  default = {"guard": "api", "passwords": "users"}
110
- guards = {"api": {"driver": "passport", "provider": "users"}}
113
+ guards = {"api": {"driver": "oauth2", "provider": "users"}}
111
114
  providers = {"users": {"driver": "masoniteorm", "model": User}}
112
115
  passwords = {
113
116
  "users": {"provider": "users", "table": "password_reset_tokens",
@@ -115,49 +118,76 @@ class Config(AuthConfig):
115
118
  }
116
119
 
117
120
 
118
- app = Application([(AuthProvider, Config)])
119
- api = app.api # the underlying FastAPI instance
121
+ app = FastAPI()
122
+ AuthProvider(Config).register(app) # always first
123
+ AuthOAuth2Provider(OAuth2Config(key="change-me-to-a-long-random-secret")).register(app)
120
124
 
121
125
 
122
- @api.get("/me")
126
+ @app.get("/me")
123
127
  def me(user=Depends(current_user)):
124
128
  return user
125
129
 
126
130
 
127
- @api.get("/reports")
131
+ @app.get("/reports")
128
132
  def reports(ctx=Depends(require_scopes("reports:read"))):
129
133
  return {"ok": True}
130
134
  ```
131
135
 
132
- Serve it:
136
+ `current_user`, `optional_user`, `auth` and `require_scopes` resolve the request
137
+ through the default guard (`AuthConfig.default["guard"]`); use `Auth.user("name")`
138
+ to read another guard.
133
139
 
134
- ```bash
135
- uvicorn myapp:app # Application is ASGI-callable
136
- uvicorn myapp:app.api # or serve the FastAPI instance directly
140
+ In a Startkit application, list `AuthProvider` and then the feature providers in
141
+ the application's providers instead:
142
+
143
+ ```python
144
+ from fastapi_startkit import Application
145
+
146
+ app = Application(
147
+ providers=[
148
+ (AuthProvider, Config),
149
+ (AuthOAuth2Provider, OAuth2Config(key="change-me-to-a-long-random-secret")),
150
+ ]
151
+ )
137
152
  ```
138
153
 
154
+ See [Registering the providers](authentication.md#registering-the-providers).
155
+
139
156
  ## Configuration
140
157
 
141
- `AuthConfig` mirrors Laravel's `config/auth.php` and adds JWT/token knobs:
158
+ `AuthConfig` mirrors Laravel's `config/auth.php`: guards, user providers and
159
+ password brokers. Each feature has its own config, handed to its provider:
160
+
161
+ | Provider | Config | Enables |
162
+ | --- | --- | --- |
163
+ | `AuthSessionProvider` | `SessionConfig` | cookie sessions (`"session"` guards), CSRF |
164
+ | `AuthOAuth2Provider` | `OAuth2Config` | the OAuth 2.1 server (`"oauth2"` guards; `"passport"` is an alias) |
165
+ | `AuthApiTokenProvider` | `ApiTokenConfig` | opaque API tokens (`"token"` guards), SPA mode |
142
166
 
143
167
  ```python
144
- class AuthConfig:
145
- default = {"guard": "api", "passwords": "users"}
146
- guards = {"api": {"driver": "passport", "provider": "users"}}
147
- providers = {"users": {"driver": "masoniteorm", "model": User}}
148
- passwords = {"users": {"provider": "users", "table": "password_reset_tokens",
149
- "expire": 60, "throttle": 60}}
150
-
151
- # token settings (all optional, sensible defaults shown)
152
- key = None # JWT secret (required in production)
153
- algorithm = "HS256"
154
- access_token_ttl = 3600 # seconds
155
- refresh_token_ttl = 60 * 60 * 24 * 14
156
- personal_access_token_ttl = 60 * 60 * 24 * 365
157
- authorization_code_ttl = 600
158
- bcrypt_rounds = 12
168
+ OAuth2Config(
169
+ key=None, # JWT secret (required in production)
170
+ algorithm="HS256",
171
+ access_token_ttl=3600,
172
+ refresh_token_ttl=60 * 60 * 24 * 14,
173
+ authorization_code_ttl=600,
174
+ issuer=None, # stamps and verifies `iss`
175
+ resources=[], # RFC 8707 resource indicators
176
+ scopes={}, # scope catalog; empty accepts any scope
177
+ default_scopes=[],
178
+ pkce_methods=["S256"],
179
+ require_pkce=True,
180
+ require_redirect_uri=True,
181
+ grant_types=["authorization_code", "client_credentials", "refresh_token"],
182
+ tokens=OAuthTokensConfig(store="memory"),
183
+ clients=OAuthClientsConfig(store="database"), # the default; needs the migrations
184
+ )
159
185
  ```
160
186
 
187
+ Add `"password"` to `grant_types` to enable the legacy password grant (and the
188
+ `/token` endpoint). The OAuth settings formerly on `AuthConfig` (`key`,
189
+ `issuer`, `scopes`, ...) are still read from there with a `DeprecationWarning`.
190
+
161
191
  ### Provider drivers
162
192
 
163
193
  | driver | meaning |
@@ -204,7 +234,7 @@ introspection re-check the token owner against it. A client without a
204
234
  client bound to a different provider with `unauthorized_client`:
205
235
 
206
236
  ```python
207
- client, secret = manager.client_repository.register(name="admin-panel", provider="admins")
237
+ client, secret = await manager.client_repository.register(name="admin-panel", provider="admins")
208
238
  ```
209
239
 
210
240
  ### ORM stores and migrations
@@ -217,20 +247,25 @@ in `fastapi-startkit[database]`). The package ships its own models
217
247
  (`fastapi_startkit_auth.orm`) and uses no raw SQL:
218
248
 
219
249
  ```python
220
- session = {"store": "orm"}
221
- api_tokens = {"store": "orm"}
222
- tokens = {"store": "orm", "connection": "auth"} # optional ORM connection name
250
+ SessionConfig(store="database")
251
+ ApiTokenConfig(store="database")
252
+ OAuth2Config(tokens=OAuthTokensConfig(store="database", connection="auth")) # optional ORM connection name
223
253
  ```
224
254
 
255
+ `"database"` is the persistent store for all four configs. Only
256
+ `OAuthClientsConfig` uses it by default; `SessionConfig`, `ApiTokenConfig` and
257
+ `OAuthTokensConfig` default to `"memory"`.
258
+
225
259
  `connection` names an entry of your database config; omit it to use the default
226
- connection. Publish and run the migrations once per app (they are reversible
227
- and create the `sessions`, `personal_api_tokens`, `oauth_access_tokens`,
228
- `oauth_refresh_tokens` and `oauth_auth_codes` tables with their indexes):
260
+ connection. Publish and run the migrations once per app. Each provider
261
+ publishes its own (all reversible and additive): sessions, personal API tokens,
262
+ and the OAuth access/refresh token, auth code and client tables plus the
263
+ `resource` and refresh `family_id` columns:
229
264
 
230
265
  ```bash
231
- python artisan provider:publish -p auth # copies them to databases/migrations/
232
- python artisan migrate
233
- python artisan migrate:rollback # drops them again
266
+ python artisan provider:publish -p auth-oauth2 # copies them to databases/migrations/
267
+ python artisan db:migrate
268
+ python artisan db:migrate:rollback # drops them again
234
269
  ```
235
270
 
236
271
  Single-use guarantees rely on conditional `UPDATE`/`DELETE` statements issued
@@ -238,7 +273,8 @@ through the ORM query builder: refresh-token rotation and authorization-code
238
273
  redemption each have exactly one winner under concurrency.
239
274
 
240
275
  The former `sql` and `async_sql` stores were removed; configuring them raises a
241
- `ValueError` pointing at `"orm"`. Use `"memory"` or an `"instance"` store for
276
+ `ValueError` pointing at `"database"`. `"orm"` is a deprecated alias of
277
+ `"database"` and emits a `DeprecationWarning`. Use `"memory"` or an `"instance"` store for
242
278
  sync setups.
243
279
 
244
280
  In mixed setups (async stores with a sync provider or a sync session store),
@@ -256,13 +292,15 @@ one bcrypt verify, the same as a wrong password.
256
292
 
257
293
  | Method & path | Purpose |
258
294
  | --- | --- |
259
- | `POST /oauth/token` | Unified token endpoint: `password`, `refresh_token`, `client_credentials`, `authorization_code` |
260
- | `POST /token` | Simple password grant (FastAPI-tutorial style) |
261
- | `POST /oauth/authorize` | Approve an auth-code request (requires an authenticated user) → returns `code` |
262
- | `POST /oauth/introspect` | RFC 7662 token introspection (**requires client authentication**) |
263
- | `POST /oauth/revoke` | RFC 7009 access/refresh token revocation (**requires client authentication**) |
264
- | `POST/GET /oauth/clients`, `DELETE /oauth/clients/{id}` | Client registration & management |
265
- | `POST/GET /oauth/personal-access-tokens`, `DELETE .../{jti}` | Personal access tokens |
295
+ | `GET /.well-known/oauth-authorization-server` | RFC 8414 server metadata |
296
+ | `POST /oauth/token` | Token endpoint: `authorization_code`, `refresh_token`, `client_credentials` (+ `password` when enabled) |
297
+ | `POST /token` | Simple password grant (only when `"password"` is enabled) |
298
+ | `GET /oauth/authorize` | Validate an auth-code request for your consent screen |
299
+ | `POST /oauth/authorize` | Issue a code once the user consents (`"approved": true`) → `code`, `iss` |
300
+ | `POST /oauth/introspect` | RFC 7662 introspection (**confidential client authentication**) |
301
+ | `POST /oauth/revoke` | RFC 7009 revocation of a token and its chain (**client authentication**) |
302
+ | `GET/DELETE /oauth/tokens`, `DELETE /oauth/tokens/{jti}` | The user's OAuth tokens |
303
+ | `POST/GET/DELETE /oauth/personal-access-tokens`, `DELETE .../{jti}` | Personal access tokens |
266
304
  | `POST /password/email` | Trigger a password-reset token (delivered out-of-band; see below) |
267
305
  | `POST /password/reset` | Reset the password with a token |
268
306
 
@@ -278,12 +316,21 @@ class AuthConfig(BaseAuthConfig):
278
316
  # debug_expose_reset_token = True # DEV ONLY: echo the token in the response
279
317
  ```
280
318
 
281
- The `code_challenge` for public clients is mandatory — a public (secretless)
282
- client cannot obtain an authorization code without PKCE, and codes are verified
283
- against the `code_verifier` at exchange.
319
+ PKCE is required by default (`require_pkce=True`, `pkce_methods=["S256"]`):
320
+ send `code_challenge_method=S256` with a 43-character challenge, and a verifier
321
+ of 43–128 characters at exchange. A missing method means `plain`.
322
+
323
+ Clients are created with `python artisan auth:oauth2:client --name app
324
+ --redirect-uri https://app/cb [--public] [--scopes "read write"]` or
325
+ `manager.client_repository.register(...)`; there are no `/oauth/clients` HTTP
326
+ routes. Refresh tokens are bound to their client. Replaying a rotated refresh
327
+ token revokes its whole family, whoever presents it; see
328
+ [Refresh tokens](authentication.md#refresh-tokens).
284
329
 
285
330
  ### Example: password grant
286
331
 
332
+ With `"password"` in `OAuth2Config.grant_types`:
333
+
287
334
  ```bash
288
335
  curl -X POST localhost:8000/oauth/token \
289
336
  -d grant_type=password -d username=ada@example.com -d password=secret -d scope="read write"
@@ -293,8 +340,8 @@ curl -X POST localhost:8000/oauth/token \
293
340
 
294
341
  ### Example: authorization code + PKCE
295
342
 
296
- 1. `POST /oauth/authorize` with a bearer token and `code_challenge` → returns a single-use `code`.
297
- 2. `POST /oauth/token` with `grant_type=authorization_code`, the `code`, and the `code_verifier`.
343
+ 1. `POST /oauth/authorize` as the signed-in user (resolved through `OAuth2Config.authorization_guard`, default: the default guard) with `client_id`, `redirect_uri`, `approved: true`, `code_challenge` and `code_challenge_method: S256` → returns a single-use `code` and `iss`.
344
+ 2. `POST /oauth/token` with `grant_type=authorization_code`, `client_id`, the `code`, `redirect_uri` and the `code_verifier` (plus the secret for a confidential client).
298
345
 
299
346
  ## Scopes / abilities
300
347