I want to add the detail to the solution mentioned briefly in my comment above. This will be important for those who develop applications with multiple tenants in Office 365, especially if the application will ever access SharePoint sites, including OneDrive.
The procedure here is a little non-standard from the point of view of OAuth 2.0, but it makes sense in a world with several tenants. The key reuses the first CODE returned from Azure. Follow me here:
First, we follow the standard OAuth authentication steps:
GET /common/oauth2/authorize?client_id=5cb5e93b-57f5-4e09-97c5-e0d20661c59a &redirect_uri=https://myappdomain.com/v1/oauth2_redirect/ &response_type=code&prompt=login&state=D79E5777 HTTP/1.1 Host: login.windows.net Cache-Control: no-cache
This redirects to the Azure login page where the user logs in. If successful, Azure then returns the code to your endpoint:
https://myappdomain.com/v1/oauth2_redirect/?code=AAABAAAA...{ONE-CODE-To-RULE-THEM-ALL}xyz
Now we go back to the /token endpoint to get the actual Token token that will be used in subsequent REST calls. Again, this is just classic OAuth2 ... but look at how we use the /Discovery endpoint as a resource - instead of any of the endpoints that we will actually use to collect data. In addition, we request the UserProfile.Read .
POST /common/oauth2/token HTTP/1.1 Host: login.windows.net Accept: text/json Cache-Control: no-cache ----WebKitFormBoundaryE19zNvXGzXaLvS5C Content-Disposition: form-data; name="grant_type" authorization_code ----WebKitFormBoundaryE19zNvXGzXaLvS5C Content-Disposition: form-data; name="code" AAABAAAA...{ONE-CODE-To-RULE-THEM-ALL}xyz ----WebKitFormBoundaryE19zNvXGzXaLvS5C Content-Disposition: form-data; name="client_id" 5cb5e93b-57f5-4e09-97c5-e0d20661c59a ----WebKitFormBoundaryE19zNvXGzXaLvS5C Content-Disposition: form-data; name="client_secret" 02{my little secret}I= ----WebKitFormBoundaryE19zNvXGzXaLvS5C Content-Disposition: form-data; name="redirect_uri" https://myappdomain.com/v1/oauth2_redirect/ ----WebKitFormBoundaryE19zNvXGzXaLvS5C Content-Disposition: form-data; name="scope" UserProfile.Read ----WebKitFormBoundaryE19zNvXGzXaLvS5C Content-Disposition: form-data; name="resource" https://api.office.com/discovery/ ----WebKitFormBoundaryE19zNvXGzXaLvS5C
The response to this POST will contain an access-token that can be used to call REST to the /Discovery endpoint.
{ "refresh-token": "AAABsvRw-mAAWHr8XOY2lVOKZNLJ{BAR}xkSAA", "resource": "https://api.office.com/discovery/", "pwd_exp": "3062796", "pwd_url": "https://portal.microsoftonline.com/ChangePassword.aspx", "expires_in": "3599", "access-token": "ey_0_J0eXAiOiJjsp6PpUhSjpXlm0{F00}-j0aLiFg", "scope": "Contacts.Read", "token-type": "Bearer", "not_before": "1422385173", "expires_on": "1422389073" }
Now, using this access-token , request the /Services endpoint to find out what else is available in Office 365 for this user.
GET /discovery/v1.0/me/services HTTP/1.1 Host: api.office.com Cache-Control: no-cache
The result will contain an array of service structures describing the various endpoints and capabilities of each endpoint.
{ "@odata.context": "https://api.office.com/discovery/v1.0/me/$metadata#allServices", "value": [ { "capability": "MyFiles", "entityKey": " MyFiles@O365 _SHAREPOINT", "providerId": "72f988bf-86f1-41af-91ab-2d7cd011db47", "serviceEndpointUri": "https://contoso-my.sharepoint.com/_api/v1.0/me", "serviceId": "O365_SHAREPOINT", "serviceName": "Office 365 SharePoint", "serviceResourceId": "https://contoso-my.sharepoint.com/" }, { "capability": "RootSite", "entityKey": " RootSite@O365 _SHAREPOINT", "providerId": "72f988bf-86f1-41af-91ab-2d7cd011db47", "serviceEndpointUri": "https://contoso.sharepoint.com/_api", "serviceId": "O365_SHAREPOINT", "serviceName": "Office 365 SharePoint", "serviceResourceId": "https://contoso.sharepoint.com/" }, { "capability": "Contacts", "entityKey": " Contacts@O365 _EXCHANGE", "providerId": "72f988bf-86f1-41af-91ab-2d7cd011db47", "serviceEndpointUri": "https://outlook.office365.com/api/v1.0", "serviceId": "O365_EXCHANGE", "serviceName": "Office 365 Exchange", "serviceResourceId": "https://outlook.office365.com/" } ] }
Now the hard part is coming ... At the moment, we know the endpoints that we really want to authenticate - some of them are specific to tenants. Usually you think we need to play OAuth2 dance again from each of these endpoints. But in this case, we can fool a little - and just POST the same CODE that we originally received from Azure - using the same HTTP request above, only changing the resource and scope fields, using serviceResourceId and capability from the service structure above. Like this:
POST /common/oauth2/token HTTP/1.1 Host: login.windows.net Accept: text/json Cache-Control: no-cache ----WebKitFormBoundaryE19zNvXGzXaLvS5C Content-Disposition: form-data; name="grant_type" authorization_code ----WebKitFormBoundaryE19zNvXGzXaLvS5C Content-Disposition: form-data; name="code" AAABAAAA...{ONE-CODE-To-RULE-THEM-ALL}xyz ----WebKitFormBoundaryE19zNvXGzXaLvS5C Content-Disposition: form-data; name="client_id" 5cb5e93b-57f5-4e09-97c5-e0d20661c59a ----WebKitFormBoundaryE19zNvXGzXaLvS5C Content-Disposition: form-data; name="client_secret" 02{my little secret}I= ----WebKitFormBoundaryE19zNvXGzXaLvS5C Content-Disposition: form-data; name="redirect_uri" https://myappdomain.com/v1/oauth2_redirect/ ----WebKitFormBoundaryE19zNvXGzXaLvS5C Content-Disposition: form-data; name="scope" MyFiles.Read ----WebKitFormBoundaryE19zNvXGzXaLvS5C Content-Disposition: form-data; name="resource" https://contoso-my.sharepoint.com/ ----WebKitFormBoundaryE19zNvXGzXaLvS5C
then do the same for the other two:
... ----WebKitFormBoundaryE19zNvXGzXaLvS5C Content-Disposition: form-data; name="scope" RootSite.Read ----WebKitFormBoundaryE19zNvXGzXaLvS5C Content-Disposition: form-data; name="resource" https:
and
... ----WebKitFormBoundaryE19zNvXGzXaLvS5C Content-Disposition: form-data; name="scope" Contacts.Read ----WebKitFormBoundaryE19zNvXGzXaLvS5C Content-Disposition: form-data; name="resource" https:
All three of these calls will result in a response, like the first POST above, providing you with an update token and an access token for each of the corresponding endpoints. All this for the price of single-user authentication only. :)
Viola! The mystery is solved - you CAN write multi-user applications for O365. :)