Aldraz
Aldraz13mo ago

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!
import requests
import json

url = "https://$CUSTOM-DOMAIN/v2/users/:userId/email"

payload = json.dumps({
"email": "mini@mouse.com",
"sendCode": {
"urlTemplate": "https://example.com/email/verify?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}"
},
"returnCode": {},
"isVerified": True
})
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer <TOKEN>'
}

response = requests.request("POST", url, headers=headers, data=payload)

print(response.text)
import requests
import json

url = "https://$CUSTOM-DOMAIN/v2/users/:userId/email"

payload = json.dumps({
"email": "mini@mouse.com",
"sendCode": {
"urlTemplate": "https://example.com/email/verify?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}"
},
"returnCode": {},
"isVerified": True
})
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer <TOKEN>'
}

response = requests.request("POST", url, headers=headers, data=payload)

print(response.text)
12 Replies
SaarPhil
SaarPhil13mo ago
Did you try setting "isVerified" to false?
Aldraz
AldrazOP13mo ago
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?
SaarPhil
SaarPhil13mo ago
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.
Aldraz
AldrazOP13mo ago
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.
SaarPhil
SaarPhil13mo ago
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?
Aldraz
AldrazOP13mo ago
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.
SaarPhil
SaarPhil13mo ago
I think we need someone of the team to have a look into that then
fabienne
fabienne13mo ago
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
Aldraz
AldrazOP13mo ago
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.
fabienne
fabienne13mo ago
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
fabienne
fabienne13mo ago
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.
SaarPhil
SaarPhil13mo ago
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.

Did you find this page helpful?