Generic update endpoints are convenient. A profile page loads a DTO, the user edits a few fields, and the server maps the result back onto the stored record. That pattern works well for a biography, display preferences, or a phone number.
It becomes dangerous when the DTO also contains a security-sensitive identifier such as the sign-in email.
The subtle problem is not validation. A perfectly formatted address can still belong to somebody else. The real question is authority: has the user proved control of the new address, or have they merely typed it into an authenticated form?
A recent change across ASP.NET Core, Blazor, and .NET MAUI reinforced a lesson I keep finding useful: sensitive identity mutations deserve intent-revealing commands, not incidental property mapping.
The hidden authority inside an ordinary field
A login email often does more than label an account. It can determine the username, receive password-reset messages, receive security notifications, and act as a lookup key in related data.
If a general profile update assigns that value directly, the endpoint quietly gains the power to move the account's identity. An authenticated session may be enough to edit a profile, but it should not automatically be enough to redirect account recovery.
This is where "the UI validates it" is not a defence. Clients change. Multiple clients may call the same API. Requests can be constructed without the UI. The invariant has to live on the server.
Separate the command from the profile update
The safer shape is deliberately more explicit.
First, the general profile update ignores the login email. The field may still be returned for display, but it is not writable through that operation. This negative rule matters: there should be no second, easier path around the verified workflow.
Second, a dedicated authenticated command starts the change. It accepts only the proposed address. The server resolves the current account from the authenticated principal rather than accepting an account identifier from the request body. That removes an entire class of "change someone else's value" mistakes.
Third, the server validates the proposal and creates a change token bound to both the account and the new address. It sends that token to the proposed address. Possession of the current session starts the request; possession of the new mailbox completes it.
Fourth, the confirmation target is built from trusted server configuration or an allowlist. A client-supplied origin should not decide where a security link points. Otherwise, a legitimate token can be wrapped in an untrusted destination.
Only the confirmation step performs the authoritative mutation.
Keep projections on the correct side of the boundary
Real systems often mirror an email into profile, contact, search, or role-specific records. Those copies create another temptation: let whichever screen is being edited update every copy, including the identity account.
That reverses the authority.
The identity workflow should own the sign-in address. After confirmation, downstream records may be synchronized from that authoritative result. A projection synchronizer should never reach back and modify the login account. Its job is to follow the decision, not make it.
This distinction also improves failure handling. If a downstream synchronization fails, the system can log the drift and repair it without pretending that the user did not successfully confirm the change.
Whether you choose immediate retry, a durable outbox, or a reconciliation job depends on the consistency needs of the system. The important part is to make the authority boundary explicit.
The trade-off
This design costs more than assigning a property.
There is a dedicated request contract, endpoint, token workflow, email template, confirmation handler, client method, and test surface. Users do not see the new value everywhere until confirmation completes. If data is duplicated, projections can temporarily lag and need an observable repair path.
That complexity is justified because it matches the actual risk. Changing a sign-in identifier is not ordinary CRUD. Treating it as a workflow makes the proof, transition, and failure modes visible.
Tests should pin the boundary
The most valuable tests are often negative:
- A blank or malformed proposal is rejected.
- The current address is rejected as a no-op.
- The account comes from authentication, not a body-supplied identifier.
- Starting the request sends a confirmation link but does not change the identity.
- The token is tied to the proposed address.
- A general profile save cannot change the login email.
- A downstream synchronizer cannot write the identity account.
- The confirmation URL uses a trusted host.
These tests document who is allowed to change what. That is more durable than testing only that a button eventually displays a success message.
A practical review habit
When reviewing a broad update DTO, stop at every field and ask:
Is possession of this form enough authority to change this value?
For many fields, the answer is yes. For login identifiers, recovery destinations, payout details, privileged roles, and consent states, it often is not.
When the answer is no, remove that property from generic mapping. Give it a dedicated command, derive the subject from authenticated context, require the appropriate proof, and test the paths that must remain impossible.
The goal is not more ceremony. It is making authority visible in the design.
Top comments (0)