Before submitting
What's the bug?
What happened
I connected one Microsoft 365 account to Calnode, then used “Connect another calendar” to connect a second Microsoft 365 account. The second connection replaced the first instead of appearing alongside it for conflict checking.
The Microsoft account picker let me select the second account, and the OAuth flow completed. Both accounts are in the same Entra tenant.
Workaround that resolved it
In the Entra app registration, I went to Token configuration → Add optional claim → ID token → email. After adding that claim and reconnecting the accounts, Calnode kept both connections.
I could not find this requirement in the deployment guide or .env.example.
Possible cause
The Microsoft calendar OAuth request asks for openid, offline_access, and Calendars.ReadWrite, but not profile or email. The connection code identifies accounts using preferred_username from the ID token, falling back to email. Microsoft requires the profile scope for preferred_username, and the email claim is not guaranteed to be present by default.
When both claims are absent, accountEmailFromIDToken returns an empty string. saveToken uses that value to find and delete an existing connection before inserting the new one, so a second account can replace the first.
Relevant code:
Microsoft OAuth scopes and account identification
Microsoft configuration example
Suggested fix
Could the calendar OAuth flow request the identity claims it needs and avoid treating a missing account identifier as a valid deduplication key? A stable identifier such as tenant ID plus object ID would also avoid using a mutable email address as the account key.
Until the code handles this, documenting the ID token email optional claim would help other self-hosted operators connect multiple Microsoft 365 accounts.
Steps to reproduce
- Connect one Microsoft 365 account to Calnode
- Connect a second Microsoft 365 account.
The second connection replaced the first instead of appearing alongside it for conflict checking.
Calnode version / commit
edge (since 0.9 doesn't pull canEdit properly from MS Graph)
How are you running Calnode?
Self-hosted — Docker
Logs, screenshots, or anything else
No response
Before submitting
What's the bug?
What happened
I connected one Microsoft 365 account to Calnode, then used “Connect another calendar” to connect a second Microsoft 365 account. The second connection replaced the first instead of appearing alongside it for conflict checking.
The Microsoft account picker let me select the second account, and the OAuth flow completed. Both accounts are in the same Entra tenant.
Workaround that resolved it
In the Entra app registration, I went to Token configuration → Add optional claim → ID token → email. After adding that claim and reconnecting the accounts, Calnode kept both connections.
I could not find this requirement in the deployment guide or .env.example.
Possible cause
The Microsoft calendar OAuth request asks for openid, offline_access, and Calendars.ReadWrite, but not profile or email. The connection code identifies accounts using preferred_username from the ID token, falling back to email. Microsoft requires the profile scope for preferred_username, and the email claim is not guaranteed to be present by default.
When both claims are absent, accountEmailFromIDToken returns an empty string. saveToken uses that value to find and delete an existing connection before inserting the new one, so a second account can replace the first.
Relevant code:
Microsoft OAuth scopes and account identification
Microsoft configuration example
Suggested fix
Could the calendar OAuth flow request the identity claims it needs and avoid treating a missing account identifier as a valid deduplication key? A stable identifier such as tenant ID plus object ID would also avoid using a mutable email address as the account key.
Until the code handles this, documenting the ID token email optional claim would help other self-hosted operators connect multiple Microsoft 365 accounts.
Steps to reproduce
The second connection replaced the first instead of appearing alongside it for conflict checking.
Calnode version / commit
edge (since 0.9 doesn't pull canEdit properly from MS Graph)
How are you running Calnode?
Self-hosted — Docker
Logs, screenshots, or anything else
No response