No verification e-mail before actual changing of the e-mail
Hey! I am trying to follow several versions of the API and none of them seems to send the user a verification code (+ link) when I use the API to change the e-mail. Basically it just applies and saves the changes into Zitadel directly without asking the user, which is my main issue. Is this mechanism something that I need to do manually, like generate a custom code and e-mail and ask the user for confirmation in my app? Or do I need to use and setup the OTP Email? Thanks for help!
12 Replies
Did you try setting "isVerified" to false?
It just tells me this:
{"code":3,"message":"proto: (line 1:53): error parsing \"isVerified\", oneof zitadel.user.v2.SetEmailRequest.verification is already set"}
Are these APIs like Update My Email, Set (v1), Change the user email (v2), etc.. even supposed to send the user the e-mail in the first place when it also rewrites the e-mail immediately in the Zitadel console? Am I maybe not supposed to be using user's access_token, but maybe the API token or Service account token, does it make a difference?Are these APIs like Update My Email, Set (v1), Change the user email (v2), etc.. even supposed to send the user the e-mail in the first place when it also rewrites the e-mail immediately in the Zitadel console?If there's a
isVerified flag to set I'd assume so. However, I do not know the code.
Maybe someone that knows the code could bring some light in.
Am I maybe not supposed to be using user's access_token, but maybe the API token or Service account token, does it make a difference?The token you're using makes a difference for sure. It tells "who" (does something) and different actors might have different permissions. Again, I don't know the code, so I could not tell whether or not and if so how the code takes that into account. In general I'd assume that only privileged users can actively maintain the
isVerified state, while non privileged users can only change their email address and the system automatically maintains the isVerified state. At least that'd be the most reasonable behavior to me.I've tried it and it doesn't work. I am suspecting it could be a bug.
isVerified must be ignored, no matter if I set it False or True, it will not send the request with it and give the same error like previously mentioned or an error that it must be set as True and can't be False. You can also only sent either sendCode or returnCode for this to work, which is supposedly correct and should work like that and makes kinda sense. But the verification is not sending no matter what combination I try and so either it's a bug or I am just not doing the right thing and I should be doing this differently, like implementing custom solution.. which honestly kinda sucks, since everything else in Zitadel works, literally everything else.Just checked with the docs..
According to the documentation you can set the verified state when changing the email address:
https://zitadel.com/docs/apis/resources/user_service_v2/user-service-set-email ¹
There are also methods to (re)send the verification code and to mark the email as verified:
https://zitadel.com/docs/apis/resources/user_service_v2/user-service-resend-email-code ²
https://zitadel.com/docs/apis/resources/user_service_v2/user-service-verify-email ³
As there are endpoints to trigger sending verification codes I'd assume:
1. change the email, set the verification state to false ¹
2. trigger the resend email code endpoint ²
3. verify the code the user returns with ³
If that doesn't work either one of the endpoints doesn't act properly or I don't understand the flow design OR .... Could it be that the error is due to the use of an e-mail address that is already registered to the system?
I am pretty sure it's buggy or not thought through, because.. the funny thing is that you can't even set the verification state to false of the same e-mail, you can however set it to another e-mail and that can be a completely different non-existent e-mail (the link with the verification will be sent there - which is funny, it's like asking the hacker for the confirmation instead of the user). And so in the Zitadel it applies the new e-mail even without confirming the verification (because technically it's overwritten, but not verified yet) and after that you can send the same request again with your previous e-mail this time and it will work and now you have un-verified e-mail and you will even receive a link to verification, so that's cool, but I am pretty sure this is not how things should work. Because now I have to have this special garbage bin e-mail disposal just for this to work.
Maybe this is meant to work via OTP E-mail and Sessions which I don't fully understand, but that would require forcing MFA I suppose and then maybe if I would want to change e-mail it would react differently, but I have doubts.
I think we need someone of the team to have a look into that then
the sendCode, returnCode and isVerified are a one of. so you only have to send one of those attributes.
with that you can tell zitadel what to do, sendCode means zitadel will send a code to the email address, return code means you will get a code back in the response and you can send if yourself and if you put isverified to true, you will not get a code.
we still try to figure out how the oneof can be documented clearly, unforuntately the generated api docs do not know one of, which makes it difficult.
sorry for the inconvinience
Yes, I know about that. I've tried all combinations already and none of them works on already verified e-mail, actually what I mean is that you can't send anything to the same e-mail that is currently set. And so you can't even change the verification to false of the same e-mail. I want the user to be able to change their e-mail via my app and for security purpose first send the link to the current e-mail to verify (again) and then after verification probably verify the new e-mail as well. Resend Email Verification APIs have the same issue.
Ok i see, yes at the moment this flow is only for verifying new email addresses.
From my understanding, you basically want to do kind of a reauthentication, to ensure that the user has permission to this. So I would say that you should be able to use the session api for that with an email otp code
you can create a session with the username and the email otp code, and the user will get that email. https://zitadel.com/docs/apis/resources/session_service_v2/session-service-create-session
ZITADEL Docs
Create a new session. A token will be returned, which is required for further updates of the session.
I'd split it into different endpoints.
For the typical CRUD operations I'd go for something like this:
PUT
/v2/users/:userId/emails
Payload: {'email': '', 'verified': true|false}
-> add an email address
POST /v2/users/:userId/email/{SOME_IDENTIFIER}
Payload: {'email': '', 'verified': true|false}
-> change an existing email address
DELETE /v2/users/:userId/email/{SOME_IDENTIFIER}
-> delete an email address
GET /v2/users/:userId/emails
-> get email addresses (with some pagination)
PATCH /v2/users/:userId/email/{SOME_IDENTIFIER}
Payload {'verified': true|false}
-> set verified state (true/ false) on an existing email address
And for verification codes I'd go for something like this:
GET /v2/users/:userId/email/{SOME_IDENTIFIER}/send-code?code=...
-> send verification code
make the code param optional to let Zitadel generate one for you in case it's not set
POST /v2/users/:userId/email/{SOME_IDENTIFIER}/verify-code
Payload: {'code': ''}
-> verify the email address
I don't like the use of GET method for the send-code endpoint, but for one optional field..
Sometimes you have to make compromises. 🙈
However, this would allow for fine-grained control, and while each endpoint has its distinct functionality, in many cases it would still be possible to update the configuration with a single request or two at max.