* fix(crypto): use authenticated encryption for IDP intent tokens
IDP intent tokens were encrypted with AES-CFB which provides no
integrity protection, making them malleable. Switch to A256GCM via
the existing AuthAlgorithm interface (EncryptToken/DecryptToken),
the same approach already used for OIDC and session tokens.
The idpConfigEncryption key type is widened from EncryptionAlgorithm
to AuthEncryptionAlgorithm so that data-at-rest encryption (IDP
client secrets, LDAP bind passwords, SAML keys) continues to use
the CFB path unmodified.
Co-authored-by: Cursor <[email protected]>
* fix(proto): raise IDP intent token max length to 500 for authenticated tokens
The switch to authenticated A256GCM (JWE compact serialization) makes IDP
intent tokens longer (~270 chars) than the previous AES-CFB base64 output.
Raise the max_len/max_length validation and OpenAPI constraints on every
idp_intent_token field from 200 to 500 so the new tokens are accepted:
user v2/v2beta (idp + user_service), session v2/v2beta, and resources
user v3alpha.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
---------
Co-authored-by: Cursor <[email protected]>
Co-authored-by: Claude Opus 4.8 <[email protected]>
# Which Problems Are Solved
TOTP codes could be reused, violating [RFC 6238, section
5.2](https://datatracker.ietf.org/doc/html/rfc6238#section-5.2).
# How the Problems Are Solved
Transmitted codes are now recorded in each success event. As the codes
amount to secrets, they are not stored verbatim but as a randomly salted
HMAC-SHA256 (`crypto.HMACValue`). Each value gets a fresh random salt,
so an equal code always produces a different stored value and two
entries can never be recognized as being derived from the same code.
This is deliberately not a replacement for proper secret storage: with
the salt stored alongside the hash and a 6 digit payload, anyone with
event access can brute-force the code in milliseconds. That is
acceptable here because a code is worthless once its window has passed.
The `HMACValue` doc comment states the same.
TOTP codes are low-entropy (6 digits), pseudo random codes. Therefore it
is possible for the same code to be generated more than once over the
lifetime of a user. In order to prevent the (unlikely) case where users
login with large time intervalls and a generated code may be equal to
the one last used a long time ago, we only check for codes used within
the recent period window.
A simpler implementation would be simply storing the latest consumed
step, but the TOTP library does not return this value. Because of the
allowed skew there are always 3 steps within the validation window, so
we can't calculate the current step based on time alone. The retained
history spans one period more than the validation window: validity is
decided by the app server's clock, while the stored events carry the
database clock, and the extra period absorbs the drift between them.
**Note:** a reused code pushes a distinct
`user.human.mfa.otp.check.reused` event, not a failed check event. A
replay therefore stays observable through the Events API without
counting towards the lockout policy. Otherwise anyone who captured a
code could replay it to lock the user out.
# Additional Changes
- Read only the latest events in recheck query
# Additional Context
- Closes https://github.com/zitadel/zitadel/issues/6452
---------
Co-authored-by: Livio Spring <[email protected]>
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: muhlemmer <[email protected]>
# Which Problems Are Solved
- The eventstore did not support intentionally overwriting the resource
owner when creating events for aggregates that may be reused across
owners.
- Resource owner handling was implicit and could not be controlled per
command/event type.
- We needed a safe way to distinguish between:
- keeping the existing aggregate owner, and
- explicitly setting a new owner for specific create-like events.
# How the Problems Are Solved
- Introduced a new eventstore command type with an explicit
enforce_owner flag.
- Updated eventstore.commands_to_events and eventstore.push so owner
assignment is now explicit:
- if enforce_owner is true, the command owner is written
- if enforce_owner is false, the existing aggregate owner is retained
when present
- Added EnforceResourceOwnerCommand and wiring so command types can opt
in to enforced owner behavior.
- Wired the new behavior through the v3 eventstore push path, including
compatibility fallback for older command type mapping.
- Added migration/setup changes to register and use the new command type
and SQL functions.
- Added and updated tests for owner overwrite and aggregate ID reuse
scenarios.
# Additional Changes
- Added small migration/setup robustness improvements related to
eventstore setup ordering and helper reuse.
- Added focused test coverage for enforced owner behavior and
sequencing.
- Events that currently allow owner changes (implement
EnforceResourceOwner) are:
- AddedEvent (action)
- GroupAddedEvent
- StartedEvent (idp intent)
- ProjectAddedEvent
- HumanAddedEvent
- HumanRegisteredEvent
- MachineAddedEvent
- CreatedEvent (schema user)
# Additional Context
- Follow-up for eventstore owner-handling correctness in create flows
and aggregate ID reuse cases.
- No additional issue link was attached for this change.
---------
Co-authored-by: abhishek kumar gupta <[email protected]>
# Which Problems Are Solved
This PR adds support for storing and retrieving refresh tokens from
external identity providers during the IDP intent flow.
# How the Problems Are Solved
* Updated `idp.proto` (v2 and v2beta): added an optional `refresh_token`
field to `IDPOAuthAccessInformation`, which, in turn, is used in
`RetrieveIdentityProviderIntentResponse`
* Added the `IDPRefreshToken` field to the IDP intent `SucceededEvent`
struct, and updated the corresponding constructor to set the refresh
token
* Added the `IDPRefreshToken` field to `IDPIntentWriteModel`, and
updated `reduceOAuthSucceededEvent` to populate refresh token from
events
* Updated `tokensForSucceededIDPIntent` function to extract and encrypt
the refresh token from IDP session, if set
* Updated `idpOAuthTokensToPb` function to decrypt refresh token before
returning to clients
* Updated unit and integration tests
# Additional Changes
Updated the link to the JWT IDP docs linked in the Console
# Additional Context
- Closes https://github.com/zitadel/zitadel/issues/11047
# Which Problems Are Solved
In login V2, when the user started the login flow of an external IDP,
there was no login_hint passed to the external provider. This required
the user to enter their username again. Whilst they already entered it
in Zitadel. By adding the `login_hint` parameter, the username should
already be filled in on the external idp.
# How the Problems Are Solved
Pass the `login_hint` parameter to the external idp.
# Additional Changes
Also added the `login_hint` to the event-store for audit trailing.
# Additional Context
- Closes#11392
---------
Co-authored-by: Copilot <[email protected]>
<!--
Please inform yourself about the contribution guidelines on submitting a
PR here:
https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#submit-a-pull-request-pr.
Take note of how PR/commit titles should be written and replace the
template texts in the sections below. Don't remove any of the sections.
It is important that the commit history clearly shows what is changed
and why.
Important: By submitting a contribution you agree to the terms from our
Licensing Policy as described here:
https://github.com/zitadel/zitadel/blob/main/LICENSING.md#community-contributions.
-->
One-time recovery codes are a common multi-factor authentication (MFA)
backup method, letting users access their account if they lose other MFA
devices. Support for recovery codes can also reduce support burden for
users locked out of their accounts and provide a more secure and
reliable form of verification than security questions.
# Which Problems Are Solved
Zitadel currently lacks support for recovery codes.
# How the Problems Are Solved
This PR partially addresses recovery code support in Zitadel.
Importantly, it adds recovery codes as a new 2FA `Factor` and an
additional `Check` type for the Session API.
```
Example recovery code flow:
1. User generates N new recovery codes using `POST /v2/users/{user_id}/recovery_codes`
2. Zitadel hashes and stores these codes and returns the un-hashed codes in the response
3. User creates new session with an additional check: `recoveryCode`
4. Code is checked against hash and, if valid, cannot be used again
5. User attempts to adds N more codes using the same endpoint
6. If `remaining_codes + N <= RecoveryCodes->MaxCount` config value, then recovery codes are added in addition to original codes
7. User can remove all recovery codes using `DEL /v2/users/:userId/recovery_codes`
```
This PR adds:
- [x] Session recovery_code check support on `POST+PATCH /v2/sessions`
endpoints
- [x] Adds `mfa_recovery_code_checked_at` column (default null) to
`projections.sessions8` table
- [x] Support for `SECOND_FACTOR_TYPE_RECOVERY_CODES` as available 2FA
method on login policy
- [x] Support for importing recovery codes in /import code
Missing, will _not_ implement in this PR:
- [ ] Admin console support for displaying Recovery Code settings for
user(s)
- [ ] Zitadel Typescript login support for recovery codes
TODO:
- [x] Additional unit and integration tests
- [x] Error translations
# Additional Changes
None
# Additional Context
- Closes#6898
---------
Co-authored-by: Livio Spring <[email protected]>
# Which Problems Are Solved
As part of our efforts to simplify the structure and versions of our
APIs, were moving all existing v2beta endpoints to v2 and deprecate
them. They will be removed in Zitadel V5.
# How the Problems Are Solved
- This PR deprecates all v2beta service and their endpoints, which have
already a corresponding v2 version and should not be used anymore.
- The comments and have been improved and, where not already done, moved
from swagger annotations to proto.
- All required fields have been marked with `(google.api.field_behavior)
= REQUIRED` and validation rules have been added where missing.
- Removed the "required flag" comments on the Action and WebKey service
endpoints, since they were removed in Zitadel v4.
- The `SetSession` endpoint already documented that the token does not
have to be provided anymore and will be ignored if still sent, but it
was actually still checked if provided. The corresponding check has been
removed and the field is now properly deprecated in the proto as well.
# Additional Changes
None
# Additional Context
- part of #10772
- requires backport to v4.x
# Which Problems Are Solved
Some OAuth2 and OIDC providers require the use of PKCE for all their
clients. While ZITADEL already recommended the same for its clients, it
did not yet support the option on the IdP configuration.
# How the Problems Are Solved
- A new boolean `use_pkce` is added to the add/update generic OAuth/OIDC
endpoints.
- A new checkbox is added to the generic OAuth and OIDC provider
templates.
- The `rp.WithPKCE` option is added to the provider if the use of PKCE
has been set.
- The `rp.WithCodeChallenge` and `rp.WithCodeVerifier` options are added
to the OIDC/Auth BeginAuth and CodeExchange function.
- Store verifier or any other persistent argument in the intent or auth
request.
- Create corresponding session object before creating the intent, to be
able to store the information.
- (refactored session structs to use a constructor for unified creation
and better overview of actual usage)
Here's a screenshot showing the URI including the PKCE params:

# Additional Changes
None.
# Additional Context
- Closes#6449
- This PR replaces the existing PR (#8228) of @doncicuto. The base he
did was cherry picked. Thank you very much for that!
---------
Co-authored-by: Miguel Cabrerizo <[email protected]>
Co-authored-by: Stefan Benz <[email protected]>
# Which Problems Are Solved
The current v3alpha actions APIs don't exactly adhere to the [new
resources API
design](https://zitadel.com/docs/apis/v3#standard-resources).
# How the Problems Are Solved
- **Improved ID access**: The aggregate ID is added to the resource
details object, so accessing resource IDs and constructing proto
messages for resources is easier
- **Explicit Instances**: Optionally, the instance can be explicitly
given in each request
- **Pagination**: A default search limit and a max search limit are
added to the defaults.yaml. They apply to the new v3 APIs (currently
only actions). The search query defaults are changed to ascending by
creation date, because this makes the pagination results the most
deterministic. The creation date is also added to the object details.
The bug with updated creation dates is fixed for executions and targets.
- **Removed Sequences**: Removed Sequence from object details and
ProcessedSequence from search details
# Additional Changes
Object details IDs are checked in unit test only if an empty ID is
expected. Centralizing the details check also makes this internal object
more flexible for future evolutions.
# Additional Context
- Closes#8169
- Depends on https://github.com/zitadel/zitadel/pull/8225
---------
Co-authored-by: Silvan <[email protected]>
Co-authored-by: Stefan Benz <[email protected]>
# Which Problems Are Solved
The session API was designed to be flexible enough for multiple use
cases / login scenarios, where the login could respect the login policy
or not. The session API itself does not have a corresponding policy and
would not check for a required MFA or alike. It therefore also did not
yet respect the lockout policy and would leave it to the login UI to
handle that.
Since the lockout policy is related to the user and not the login
itself, we decided to handle the lockout also on calls of the session
API.
# How the Problems Are Solved
If a lockout policy is set for either password or (T)OTP checks, the
corresponding check on the session API be run against the lockout check.
This means that any failed check, regardless if occurred in the session
API or the current hosted login will be counted against the maximum
allowed checks of that authentication mechanism. TOTP, OTP SMS and OTP
Email are each treated as a separate mechanism.
For implementation:
- The existing lockout check functions were refactored to be usable for
session API calls.
- `SessionCommand` type now returns not only an error, but also
`[]eventstore.Command`
- these will be executed in case of an error
# Additional Changes
None.
# Additional Context
Closes#7967
---------
Co-authored-by: Elio Bischof <[email protected]>
# Which Problems Are Solved
If an IdP intent succeeded with the user was not linked yet, the IdP
link was then added, the following IdP check on the session API would
then fail with `Intent meant for another user (COMMAND-O8xk3w)`.
This issue was introduced with when allowing IdP intents from other
organizations (https://github.com/zitadel/zitadel/pull/7871)
# How the Problems Are Solved
The IdP link is now correctly checked in the session API (using the
user's organization instead of the one from the intent).
# Additional Changes
- Improved the corresponding integration test to cover the exact
bahvior.
- Tests, which had to be updated with newer cases where additionally
changed to use expectEventstore instead of deprecated eventstoreExpect
and the two eventstore mocks of the session_tests.go where combined.
# Additional Context
- Relates to #7871
- This issue was reported by a customer.
- will be back ported to 2.52.x
# Which Problems Are Solved
The session update requires the current session token as argument.
Since this adds extra complexity but no real additional security and
prevents case like magic links, we want to remove this requirement.
We still require the session token on other resouces / endpoints, e.g.
for finalizing the auth request or on idp intents.
# How the Problems Are Solved
- Removed the session token verifier in the Update Session GRPc call.
- Removed the session token from login UI examples session update calls
# Additional Changes
- none
# Additional Context
- Closes#7883
* implement code exchange
* port tokenexchange to v2 tokens
* implement refresh token
* implement client credentials
* implement jwt profile
* implement device token
* cleanup unused code
* fix current unit tests
* add user agent unit test
* unit test domain package
* need refresh token as argument
* test commands create oidc session
* test commands device auth
* fix device auth build error
* implicit for oidc session API
* implement authorize callback handler for legacy implicit mode
* upgrade oidc module to working draft
* add missing auth methods and time
* handle all errors in defer
* do not fail auth request on error
the oauth2 Go client automagically retries on any error. If we fail the auth request on the first error, the next attempt will always fail with the Errors.AuthRequest.NoCode, because the auth request state is already set to failed.
The original error is then already lost and the oauth2 library does not return the original error.
Therefore we should not fail the auth request.
Might be worth discussing and perhaps send a bug report to Oauth2?
* fix code flow tests by explicitly setting code exchanged
* fix unit tests in command package
* return allowed scope from client credential client
* add device auth done reducer
* carry nonce thru session into ID token
* fix token exchange integration tests
* allow project role scope prefix in client credentials client
* gci formatting
* do not return refresh token in client credentials and jwt profile
* check org scope
* solve linting issue on authorize callback error
* end session based on v2 session ID
* use preferred language and user agent ID for v2 access tokens
* pin oidc v3.23.2
* add integration test for jwt profile and client credentials with org scopes
* refresh token v1 to v2
* add user token v2 audit event
* add activity trigger
* cleanup and set panics for unused methods
* use the encrypted code for v1 auth request get by code
* add missing event translation
* fix pipeline errors (hopefully)
* fix another test
* revert pointer usage of preferred language
* solve browser info panic in device auth
* remove duplicate entries in AMRToAuthMethodTypes to prevent future `mfa` claim
* revoke v1 refresh token to prevent reuse
* fix terminate oidc session
* always return a new refresh toke in refresh token grant
---------
Co-authored-by: Livio Spring <[email protected]>
* fix: import totp in add human user with secret
* fix: import totp in add human user with secret
* fix: import totp in add human user with secret
* fix: review comment changes
* fix: correct resourceowner of intent to instance
* fix: correct resourceowner of intent to instance
* fix: correct resourceowner of intent to instance
* fix: correct resourceowner of intent to instance
* fix: correct resourceowner of intent to instance
* docs: expand the login example with org specific parameters
* fix: existence of idp is not checked through resourceowner
* fix: existence of idp is not checked through resourceowner
* fix: existence of idp is not checked through resourceowner
---------
Co-authored-by: Livio Spring <[email protected]>
This change adds a core_generate_all make target.
It installs the required tools and runs generate on the complete project.
`golang/mock` is no longer maintained and a fork is available
from the Uber folks. So the latter is used as tool.
All the mock files have been regenerated and are part of the PR.
The obsolete `tools` directory has been removed,
as all the tools are now part of specific make targets.
Co-authored-by: Silvan <[email protected]>
* fix: add resource owner of user and change the one of session to instance
* use user resource owner from session projection
* fix session permission check
* integration tests and fixes
* update api docs
This implementation increases parallel write capabilities of the eventstore.
Please have a look at the technical advisories: [05](https://zitadel.com/docs/support/advisory/a10005) and [06](https://zitadel.com/docs/support/advisory/a10006).
The implementation of eventstore.push is rewritten and stored events are migrated to a new table `eventstore.events2`.
If you are using cockroach: make sure that the database user of ZITADEL has `VIEWACTIVITY` grant. This is used to query events.
* feat: use passwap for human user passwords
* fix tests
* passwap config
* add the event mapper
* cleanup query side and api
* solve linting errors
* regression test
* try to fix linter errors again
* pass systemdefaults into externalConfigChange migration
* fix: user password set in auth view
* pin passwap v0.2.0
* v2: validate hashed password hash based on prefix
* resolve remaining comments
* add error tag and translation for unsupported hash encoding
* fix unit test
---------
Co-authored-by: Livio Spring <[email protected]>
This fix provides a possibility to pass a domain on the session, which
will be used (as rpID) to create a passkey / u2f assertion and
attestation. This is useful in cases where the login UI is served under
a different domain / origin than the ZITADEL API.