Providers
Providers copied to clipboard
Microsoft provider fails when tenant is `common` for Graph GET organization
Discussed in https://github.com/SocialiteProviders/Providers/discussions/1027
Originally posted by theodson May 23, 2023 Calls to https://graph.microsoft.com/v1.0/organization fail for me when the tenant config is set to 'common'.
Given when the config('services.microsoft.tenant') is set to common
I experience a failure with a 500 response and message "Unable to find target address".
🤷🏼♂️ Is this a real issue or am I configuring/using the provider incorrectly ?
Supporting Information regarding tenant/org.
article 1 - Permission types
Microsoft's docs suggest that querying the organisation is not supported for "Delegated (personal Microsoft account)". This holds true in my scenario.
https://learn.microsoft.com/en-us/graph/api/organization-get
One of the following permissions is required to call this API
Permission type | Permissions (from least to most privileged) |
---|---|
Delegated (work or school account) | User.Read, Organization.Read.All, Directory.Read.All, Organization.ReadWrite.All, Directory.ReadWrite.All |
Delegated (personal Microsoft account) | Not supported. |
Application | Organization.Read.All, Directory.Read.All, Organization.ReadWrite.All, Directory.ReadWrite.All |
article 2 - Issuer values
See https://learn.microsoft.com/en-au/azure/active-directory/develop/active-directory-v2-protocols#endpoints
NOTE: These are examples. Endpoint URI format may vary based on application type, sign-in audience, and Azure cloud instance (global or national cloud).
The {issuer} value in the path of the request can be used to control who can sign into the application. The allowed values are
- common for both Microsoft accounts and work or school accounts,
- organizations for work or school accounts only,
- consumers for Microsoft accounts only,
- and tenant identifiers such as the tenant ID or domain name.
Test Changes made
As an experiment the following tests work for me . They both required code changes to the "tenant check" in src/Microsoft/Provider.php from ===
to !==
.
that now looks like
if ($this->getConfig('tenant', 'common') !== 'common' && $this->getConfig('include_tenant_info', false))
test 1 - tenant specific
- change config('services.microsoft.tenant') to
<my-tenant-id>
outcome for
- user info returned
- the tenant (organisation) information is returned for the specified tenant
<my-tenant>
outcome for other organisation user
- other organisation user accounts able NOT able to login/auth
outcome for public user
- public accounts able NOT able to login/auth
test 2 - multi-tenant capable - issuer organizations
- change config('services.microsoft.tenant') to
organizations
outcome for any organisation user
- user info returned
- the tenant (organisation) information is returned for ANY user's organisation (limited testing suggests this is the case)
outcome for public user
- public accounts able NOT able to login/auth
test 3 - multi-tenant capable - issuer common
- change config('services.microsoft.tenant') to
common
outcome for any organisation user
- user info returned
- the tenant (organisation) information is NOT returned for ANY user's organisation
outcome for public user
- public accounts able able to login/auth
- user info returned
Note: my Azure App is configured for any organisation AD or personnel Account
@allw1994 curious to know what config you've used to get Tenant data when issuer is configured to use common
based on the PR https://github.com/SocialiteProviders/Providers/pull/971. Appreciate any insight.
Yeah sure. I called my env variables the following:
MICROSOFT_CLIENT_ID=(client ID)
MICROSOFT_CLIENT_SECRET=(Secret)
MICROSOFT_CALLBACK_URI=(myurl.com/auth/microsoft/callback)
MICROSOFT_TENANT_ID="common"
Within services.php config I also have the following keys:
'microsoft' => [
'client_id' => env('MICROSOFT_CLIENT_ID'),
'client_secret' => env('MICROSOFT_CLIENT_SECRET'),
'redirect' => env('MICROSOFT_REDIRECT_URI'),
'tenant' => env('MICROSOFT_TENANT_ID'),
'include_tenant_info' => true,
'include_avatar' => true,
'include_avatar_size' => '648x648',
],
All the Socialite driver does is polls the API for the user information, any logged-in user can access their "home" tenant ID/info so provided you are able to get the user data then you may be missing a key somewhere. IIRC Microsoft does not allow you to poll for organization info when you are also able to log in with a personal account. In order to collect organization info you should be logging in with a work or school account only.
Have you tried switching to just work or school?
What are you passing to the driver?
For test 2 are you still using MICROSOFT_TENANT_ID="common" or are you using an example tenant?
More than happy to help but we may need a little more context.
Thanks that makes sense and aligns with my findings... I think you've clarified things. I'd actually isolated things using Postman to query the organization endpoint with varying user account type.
Yes the services.php config is the same as you have listed (note include_tenant_info is true)
'microsoft' => [
'client_id' => env('MICROSOFT_CLIENT_ID'),
'client_secret' => env('MICROSOFT_CLIENT_SECRET'),
'redirect' => env('MICROSOFT_REDIRECT_URI'),
'tenant' => env('MICROSOFT_TENANT_ID'),
'include_tenant_info' => true,
'include_avatar' => true,
'include_avatar_size' => '648x648',
],
What I have changed in the config is the 'tenant' value. Given that common
is referred so as an issuer type in the docs https://learn.microsoft.com/en-au/azure/active-directory/develop/active-directory-v2-protocols#endpoints
The {issuer} value in the path of the request can be used to control who can sign into the application.
-
common
for both Microsoft accounts and work or school accounts, -
organizations
for work or school accounts only, -
consumers
for Microsoft accounts only, - and
tenant identifiers
such as the tenant ID or domain name.
E.g. the auth url reads https://login.microsoftonline.com/{{issuer}}/oauth2/v2.0/authorize
The auth works as expected (you can't login consumer accounts when using organizations
as the issuer value in the auth URL, both consumer and work/school accounts are able to login when using commons
.. all as expected ).
Of course I had to modify the Provider.php code to allow non common
tenant/issuer values to be tested, but thats getting a little of track.. I'll try and pull it back to the simple issue I'm having.
The actual issue: when include_tenant_info=true
I can't login ALL user account types.
To give some context to the real problem I'm having please consider a simple trivial scenario...
allow any (work/school or consumer) user account to login to a laravel app, upon successful auth store both user and tenant data in the database and present a tenant specific welcome page or a public consumer page both displaying the user's data
As the package stands
- I'm able to achieve this using
MICROSOFT_TENANT_ID="common"
only for work/school accounts. - If a consumer (public) account attempts this then the organization / tenant code fails
Server error: GET https://graph.microsoft.com/v1.0/organization .....500 Internal Server Error
, no user info returned just an exception thrown.
I was hoping for a solution "out of the box" that supported this scenario.
I guess for public I could try/catch and retry without tenant info being requested but seems a little gross..
Route::get('/oauth/callback', function () {
try {
$oAuthUser = Socialite::driver('microsoft')->user();
} catch (\GuzzleHttp\Exception\ServerException $e) {
$include_tenant_info = false;
$oAuthUser = Socialite::driver($social_type)
->setConfig(new \SocialiteProviders\Manager\Config(
key: config('services.microsoft.client_id'),
secret: config('services.microsoft.client_secret'),
callbackUri: url(config('services.microsoft.redirect')),
additionalProviderConfig: [
'fields' => config('services.microsoft.fields', []),
'tenant' => config('services.microsoft.tenant', 'common'),
'tenant_fields' => config('services.microsoft.tenant_fields', []),
'include_tenant_info' => $include_tenant_info,
'include_avatar' => config('services.microsoft.include_avatar', true),
]
))
->user();
}
As an aside and an alternative approach to determine if ANY account was a business/work tenant or public I had considered changing the scope from User.Read
to additionally include openid profile User.Read
which allows an ID_TOKEN to be returned whereupon it could be decoded (JWT) to allow a tid
value to be extracted as part of the claims.
I've since discovered that ID_TOKEN for all account types contain a tid
value - for public accounts the tid is always 9188040d-6c67-4c5b-b112-36a304b66dad
.
https://learn.microsoft.com/en-us/answers/questions/1289403/determine-organisationid-tenant-upon-oauth2-login.
I've forked and put down a rough implementation as described above if of any interest.. https://github.com/theodson/Microsoft/tree/feature/support_all_work_school_consumer_user_accounts_under_common
Hello @theodson! I think my PR fixes it. check #1035 Strange that Microsoft API returns 500 if a user is not in any organization, instead of 404 or null
@CosminBd this can be closed now right? As the PR is merged?
Hi I don't mind if you want to close this... Personally I don't believe PR https://github.com/SocialiteProviders/Providers/pull/1035 is the best fix so I am happy to use my forked Repo and its solution.
https://github.com/SocialiteProviders/Microsoft/compare/master...theodson:Microsoft:feature/support_all_work_school_consumer_user_accounts_under_common
Thanks all.
@theodson we're going to consolidate the providers soon, are you still having this issue? I would be happy to accept a PR to merge your changes, with a few minor changes (add firebase library, etc).
@atymic I'am still having this issue.
Result is Server error: GET https://graph.microsoft.com/v1.0/organization .....500 Internal Server Error as @theodson
GuzzleHttp \ Exception \ ServerException when logging in with a personal Microsoft account with include_tenant_info enabled in common mode.
Options I have used;
'tenant' => 'common', 'include_tenant_info' => true,
Disabling include_tenant_info allows me to log in with a personal account, but the tenant info is not included for work/school accounts.