Skip to content

feat: Added Microsoft Entra ID provider to OAuth2 plugin - #132

Closed
masb0ymas wants to merge 5 commits into
Authula:mainfrom
masb0ymas:main
Closed

masb0ymas wants to merge 5 commits into
Authula:mainfrom
masb0ymas:main

Conversation

@masb0ymas

Copy link
Copy Markdown

Summary

Adds Microsoft (Entra ID v2) as a built-in OAuth2 provider. Microsoft never verifies the mail/UPN it returns, and a tenant admin can set it to any address. The old callback linked a login to any existing user with the same email, so adding Microsoft as-is would allow account takeover ("nOAuth"). This PR also fixes how the callback picks the user.

plugins/oauth2/  ├── plugin.go                      # + register "microsoft", env MICROSOFT_* (+ optional TENANT_ID)
├── services/ +│   ├── microsoft_provider.go      # AzureAD(tenant) endpoint, Graph /v1.0/me, id = oid
│   ├── google_provider.go         # + EmailVerified from verified_email  │   ├── discord_provider.go        # + EmailVerified from verified
│   └── github_provider.go         # + EmailVerified (GitHub only exposes verified emails)
├── types/
│   ├── types.go                   # + UserInfo.EmailVerified
│   └── config.go                  # + ProviderConfig.TenantID
└── usecases/
    └── callback_usecase.go        # resolveUser()

How the callback picks the user now:

 Callback
   exchange code, GetUserInfo
-  user = GetByEmail(email) ?? Create(emailVerified: true)
-  if account exists: UpdateFields(account.ID, plaintext tokens)   # no-op: repo filters on user_id
-  else: CreateOAuth2(...)
+  resolveUser
+    reject if account id or email is empty
+    if account(provider, accountID) exists -> its user        # oid beats a changed email
+    else if user with this email exists
+      reject unless provider verified the email               # blocks nOAuth takeover
+    else Create(emailVerified: provider value)
+  CreateOAuth2(...)                                          # upserts and encrypts tokens
   create session

Microsoft config:

env config providers.microsoft
client id MICROSOFT_CLIENT_ID client_id
client secret MICROSOFT_CLIENT_SECRET client_secret
tenant MICROSOFT_TENANT_ID tenant_id

Tenant defaults to common. Other values: organizations, consumers, or a tenant ID. Redirect URI is {BaseURL}{BasePath}/oauth2/callback/microsoft.

Evidence

  • Before: the plugin had no tests. Reading the code: a provider identity whose unverified email matches an existing user was logged in as that user. On re-login, token refresh never ran, because UpdateFields matched user_id against the account ID.
    After: go test -race ./... passes. New tests:
TestCallbackUseCase_ResolveUser
  rejects missing account id                                        PASS
  rejects missing email                                             PASS
  known provider account returns its user even when email changed   PASS
  verified email links to existing user                             PASS
  unverified email never links to existing user                     PASS  (Create not called)
  new user keeps provider email verification status                 PASS
TestMicrosoftUserInfo
  uses mail when present                                            PASS
  falls back to userPrincipalName for personal accounts             PASS
TestNewMicrosoftProvider_TenantEndpoint                             PASS

Merge Danger

Door: two-way, with one sticky choice.

Reverting the code is easy. But Microsoft accounts are stored with the Graph object ID (oid) as account_id. Switching to another identifier later (for example the OIDC sub) would need a data migration.

Blast Radius: every OAuth2 login.

  • The callback is shared, so Google, GitHub and Discord also go through resolveUser.
  • A user who has an email/password account and then signs in with Microsoft (or with Discord using an unverified email) now gets email is not verified by <provider>; sign in with your existing method first. Linking from a logged-in session is not implemented yet.
  • New OAuth users get email_verified from the provider instead of always true. This can affect flows gated on verified email.
  • Logins where the provider returns no email are now rejected. Before, a user with an empty email was created (GitHub with no verified email).

@m-t-a97

m-t-a97 commented Nov 4, 2026

Copy link
Copy Markdown
Member

Thanks for raising this issue. I was planning on refactoring the OAuth2 plugin as I came across some critical issues with the implementation and was planning to fix it anyways within the next couple of days.

I'll provide a separate PR fixing the main issues around OAuth2 and making it more robust then you can rebase on this branch and incorporate just the Microsoft provider. Ideally we don't want multiple things being implemented like this in a PR as I prefer to separate things out when it comes to features etc.

Looking forward to getting your contribution merged in though. Bear with me in the meantime until I get these fixes in. I'll ping you on this GitHub issue thread here once the fixes are merged in.

@masb0ymas

Copy link
Copy Markdown
Author

Thanks for raising this issue. I was planning on refactoring the OAuth2 plugin as I came across some critical issues with the implementation and was planning to fix it anyways within the next couple of days.

I'll provide a separate PR fixing the main issues around OAuth2 and making it more robust then you can rebase on this branch and incorporate just the Microsoft provider. Ideally we don't want multiple things being implemented like this in a PR as I prefer to separate things out when it comes to features etc.

Looking forward to getting your contribution merged in though. Bear with me in the meantime until I get these fixes in. I'll ping you on this GitHub issue thread here once the fixes are merged in.

Sounds good. I'll wait for the OAuth2 refactor, then rebase and keep only the Microsoft Entra ID provider.

The verified-email requirement for account linking goes into its own PR against your refactored plugin, since it's a separate concern. If your refactor already covers it, say so and I'll drop it rather than duplicate work.

Ping me here when the fixes land.

@m-t-a97

m-t-a97 commented Nov 4, 2026

Copy link
Copy Markdown
Member

You can now rebase against the main branch and keep only the microsoft provider code. The email_verified property has already been implemented for OAuth2 so you don't need that either in your PR.

@m-t-a97

m-t-a97 commented Nov 4, 2026

Copy link
Copy Markdown
Member

Also make sure to update the title of the PR to something like: "feat: Added Microsoft Entra ID provider to OAuth2 plugin".

@masb0ymas masb0ymas changed the title feat(oauth2): add Microsoft Entra ID provider and require verified email for account linking feat: Added Microsoft Entra ID provider to OAuth2 plugin Nov 5, 2026
@masb0ymas

Copy link
Copy Markdown
Author

You can now rebase against the main branch and keep only the microsoft provider code. The email_verified property has already been implemented for OAuth2 so you don't need that either in your PR.

I have already resolved the file conflict.

@masb0ymas

Copy link
Copy Markdown
Author

Also make sure to update the title of the PR to something like: "feat: Added Microsoft Entra ID provider to OAuth2 plugin".

Sure, i have changed the title

@m-t-a97

m-t-a97 commented Nov 5, 2026

Copy link
Copy Markdown
Member

Can you resubmit this PR under a proper branch because I don't want to merge your main branch into our main branch. Please create a proper branch like so: "feat/microsoft-entra-id-oauth2-provider" and then push it again and open a PR.

@masb0ymas

Copy link
Copy Markdown
Author

Can you resubmit this PR under a proper branch because I don't want to merge your main branch into our main branch. Please create a proper branch like so: "feat/microsoft-entra-id-oauth2-provider" and then push it again and open a PR.


Screenshot 2026-11-05 at 14 24 54

Like this if i want to create new PR

@m-t-a97

m-t-a97 commented Nov 5, 2026

Copy link
Copy Markdown
Member

Can you resubmit this PR under a proper branch because I don't want to merge your main branch into our main branch. Please create a proper branch like so: "feat/microsoft-entra-id-oauth2-provider" and then push it again and open a PR.

Screenshot 2026-11-05 at 14 24 54 Like this if i want to create new PR

You can rebase against the Authula main branch and then squash all your commits into one. Then after you can do a git reset --soft HEAD~1 which will pop off your changes into your staged working tree and then from there you can create a new branch by doing git switch -c feat/microsoft-entra-id-oauth2-provider. Then finally commit and push. You can also go back to your local main branch and reset it back to follow the Authula main branch so it's in sync.

@masb0ymas

Copy link
Copy Markdown
Author

I have created the new PR, and i want to close this PR

@masb0ymas masb0ymas closed this Nov 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants