Azure AD automatically added offline_access - azure-active-directory

For Microsoft OAuth 2.0 auth code grant, we have encountered an issue with scopes.
When we requestion only the User.Read scope, our client is asked to grant permission to us for Sign you in and read your profile and Access your data anytime. Where we didn't state we need offline_access scope.
This is only happening after Microsoft switched to new permission grant interface. Have someone else encounter the same issue or we did something wrong?
The response_type we pass in is code only.
I have double checked, the application we registered is under https://apps.dev.microsoft.com.
The URL we use for authorizing is following.
https://login.microsoftonline.com/common/oauth2/v2.0/authorize
As I said earlier, the only scope we pass in through query was User.Read.
Edit 3
Request URL: (I have removed client id.)
https://login.microsoftonline.com:443/common/oauth2/v2.0/authorize?client_id={client_id}&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A19974%2Fapi%2Fv1%2Fmicrosoft%2Foauth2%2Fsession&response_mode=form_post&scope=User.Read&state=1527572151-IIZ0D&nonce=1527572151-IIZ0D&prompt=consent&domain_hint=organizations
Response that logged with fiddler:
POST http://localhost:19974/api/v1/microsoft/oauth2/session HTTP/1.1
Host: localhost:19974
Connection: keep-alive
Content-Length: 798
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: null
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: en,en-NZ;q=0.9,zh-TW;q=0.8,zh;q=0.7,zh-CN;q=0.6
DNT: 1
code=OAQABAAIAAADX8GCi6Js6SK82TsD2Pb7rUmGhJoHUB3devvTffqTlhRhg9XZ202zgEA8B37CzgkeLNVBc4FFstw3sTjNmYhKCYLE_jcl7KeCrtYgPVFYOKUuazv_B3vHKIM8ttwIzOlV_3GL4vqxPgjvXbWUdas5Sj9Z1X9fEBB63Wa1Ig0AnisnHk6qagIimFEPApYx473RzgIve2erM3r5fnX5Q0L1-pHppSFUJoWop6MPTkUh-umPzuXQgB280rHyUds3odS6_cJP6SbI70aLNOqHV_AnaV_VUZqQ6hLfBZMVKFMYMg_r_harPOU5EE2gf2d15FIKMsmjPRTR2vryaJRyg0TblF_jr-kWyeURwpbkPzsU6r3avEqM6dfTqhhASoXB4VmeZ2zw75pZgK4v8cfcd3J_tIpFRjcEY1TqPz5E3QrYQGfFSeBEEbjwqvj2X5_4VBvve7ABdrt3OCjid8E_837mLX-Fv5t3nk_nfnV0SY6XrFQQmoPClyqSyn44FTv_WFY7Af74SfeBrWDYSSiTuwphEmVTeT6U2R4Rs4wR8G0uHW2L53U-4UbkODd-_-JZYIahAohDAF-8TaguUwb4mOK497wsFOkgpmYz-np4MX3sTweSLmn6bAOy9Y91E3o4fuERzX9m9N_HBt64cv6k8JROKJqs6cx1Gb9EoYCRLCn2ihWi_crZh2PH5LACMCLWYgH0gAA&state=1527572151-IIZ0D&session_state=1faeaab9-0f00-45cb-a776-356463a54684
Edit 4
Today, I have done few more testing while upgrading project to .Net Core 2.1. I have notice that even though from interface it is confirming Access your data anytime, but when I use code to exchange access token, it doesn't contain refresh token.
The other thing I have notice is, when I pass scope as User.Read, and when I exchange access token, the scope came back as: User.Read User.ReadBasic.All. This is a bit of inconsistent, but not big issue.

It’s not currently possible to remove the offline_access scope from the initial consent screen when using the v2 endpoint with an AAD account. When requesting tokens the offline_access scope is still explicitly requested though.

This is an issue which is in a planned state on Azure Active Directory suggestions and feedback site.
Admin's post (Oct 2, 2018) mentions a plan to fix this "within the next 3 months".

Cuase:
For v1 endpoint, the scope isstatically configured in AAD App registration. If you have add access user's data anytime permission, you will also get the offline_access scope in your request.
Solution:
If you don't want to let user have offline_access permission, you can unpick up the Access user's data anytime permission in Microsoft Graph delegated permissions.
More about offline_access :
offline_access is one of OpenID permissions. It's name is offline_access and it's Display String in v1 endpoint is Access user's data anytime.You can see more details about this permission in this documentation.

Related

401 Unauthorized on Dataverse Web API post method

I am using Dataverse via the Web API to access data from a server. This works very well in itself. All unlocked tables/entities can be accessed via the corresponding URL using GET method and also return valid results. That means the access and the authentication via security token works.
However, when I call the POST-Method to exactly the same table/entity, I get an error message back:
__checkpoint ⇢ 401 UNAUTHORIZED from POST https://ANONYM.crm4.dynamics.com/api/data/v9.2/
The role assigned to the application user actually has explicit write permissions to the table.
The post looks like this:
POST /api/data/v9.2/ANONYMtime-acquisitions HTTP/1.1
user-agent: ReactorNetty/1.1.2
host: ANONYM.crm4.dynamics.com
ACCEPT: */*
content-type: application/json
content-length: 282
{"cr2a0_id":0,"cr2a0_Abweichung":7.5,"cr2a0_Datum":"Sat Feb 01 00:00:00 CET 2020","cr2a0_Ist-
Arbeitszeit":7.5,"cr2a0_Mitarbeiter":"ANONYM","cr2a0_Pause":0. 0,"cr2a0_Soll-
Arbeitszeit":0.0,"cr2a0_Wochentag":"ANONYM","cr2a0_Beginn":"09:00","cr2a0_Ende":"16:30",
"cr2a0_Info":""}
I would have expected that the writing access also works. What else could be the reason for this?
I tried to reproduce the same in my environment and got the same error like below:
I created an Azure AD Application and added API permission:
I generated the auth-code by using below endpoint:
https://login.microsoftonline.com/TenantID/oauth2/v2.0/authorize?
&client_id=ClientID
&response_type=code
&redirect_uri=redirectUri
&response_mode=query
&scope=https://admin.services.crm.dynamics.com/user_impersonation
&state=12345
I generated the access token by using below parameters:
GET https://login.microsoftonline.com/TenantID/oauth2/v2.0/token
client_id:ClientID
client_secret:ClientSecret
scope:https://admin.services.crm.dynamics.com/user_impersonation
grant_type:authorization_code
redirect_uri:redirectURi
code:code
By using the above access token I am able to make the GET request successfully as below:
If still the issue occurs, make sure to assign Security role like below:
And check the version of the CRM like below:
To make a POST query, refer the below blog:
Dynamics CRM 365 WEB API Common Errors and Resolution – xrm CRM Dynamics by Bipin Kumar

Token refresh fails with invalid_client error

I have a refresh token issued by app A. This refresh token is stored in an Azure Key Vault, to which app B has access. App B now takes this refreh token and exchanges it for an access token.
Unfortunately this exchange fails with the message
"error": "invalid_client",
"error_description": "AADSTS7000215: Invalid client secret is provided."
The client secret is correct though. I was able to acquire an access token to the Key Vault with it.
This is the HTTP request for the refresh token exchange taken from Fiddler (I have removed all secrets and ids):
POST https://login.microsoftonline.com/{TenantId}/oauth2/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.17763.134
Host: login.microsoftonline.com
Content-Length: 1221
Connection: Keep-Alive
grant_type=refresh_token
&client_id={ClientId}
&client_secret={ClientSecret}
&resource=https%3A%2F%2Fvault.azure.net
&redirect_uri=https%3A%2F%2Flocalhost%2F
&refresh_token={RefreshToken}
What is going here?
PS: I know it's wild storing a refresh token in a Key Vault, but that's Microsofts recommended way of accessing the CSP Partner API.
Same as the Rohit said, the resource should be the app that you want to access.
For the details about this, you could refer to here.

Azure AD app with Application.ReadWrite.OwnedBy cannot add more owners to apps it created

I have an AAD app that creates other AAD apps. The first app (creator) has right Application.ReadWrite.OwnedBy and is able to update display names of the apps it created but it is not able to add more owners on those apps. Does it need more privileges to do that?
HTTP request updating the created-app display name (succeeds):
PATCH https://graph.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/applications/a1236923-6de6-4e78-87dd-494d621fc20c?api-version=1.6 HTTP/1.1
Authorization: Bearer eyJ0eXAi...
Content-Type: application/json; charset=utf-8
Host: graph.windows.net
Content-Length: 45
Expect: 100-continue
{
"displayName": "test"
}
HTTP request adding an owner to the created-app (fails):
POST https://graph.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/applications/a1236923-6de6-4e78-87dd-494d621fc20c/$links/owners?api-version=1.6 HTTP/1.1
Authorization: Bearer eyJ0eXAi...
Content-Type: application/json; charset=utf-8
Host: graph.windows.net
Content-Length: 122
Expect: 100-continue
{
"url": "https://graph.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/users/ac990eb3-b25a-4e06-ade5-41c7613693ff"
}
HTTP/1.1 403 Forbidden
request-id: 9be47e62-abcd-4768-926f-ffd62544e696
client-request-id: 149115f6-c9b7-4dd4-a267-711a40c51f23
...
{"odata.error":{"code":"Authorization_RequestDenied","message":{"lang":"en","value":"Insufficient privileges to complete the operation."}}}
I verified that the creator app does indeed have Application.ReadWrite.OwnedBy in the roles array of the JWT Bearer token.
I also verified that the creator app is present in the owner list of the created app by calling GET https://graph.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/applications/a1236923-6de6-4e78-87dd-494d621fc20c/owners/64898a3a-2fb1-45f0-a514-e83fabbc01f2.
In general, in order to add an owner, the calling app (and the calling user, in the case of delegated permissions, though in this case it's an app only) needs to have the permission to read the object of the owner being added, as well as the permission to read and write to the object which is being given a new owner.
Thus, in order for an app to add a User object as an owner to an Application object that the calling app is itself an owner of, the app needs (at least) Directory.Read.All (to read the new owner's User object), and Application.ReadWrite.OwnedBy (to be able to write to the Application object's list of owners).
According to your error message, you do not have enough privileges. As far as I known, if you want to assign user to AAD Application with Application privileges, you need to have these privileges : Application.ReadWrite.OwnedBy and Directory.Read.All, Application.ReadWrite.All and Directory.Read.All.
Besides, Microsoft strongly recommends that you use Microsoft Graph instead of Azure AD Graph API to access Azure Active Directory resources. For more details, please refer to the article. If you want to use Microsoft graph api to assign user to AAD Application, please refer to the document.

Single page application with HttpOnly cookie-based authentication and session management

For several days now I've been looking for a secure authentication and session management mechanism for my single page application. Judging by the numerous tutorials and blog posts out there about SPAs and authentication, storing JWTs in localStorage or regular cookies seems to be the most common approach, but it's simply not a good idea to use JWTs for sessions so it's not an option for this app.
Requirements
Logins should be revokable. For example, if suspicious activity is detected on a user's account, it should be possible to revoke that user's access immediately and require a new log in. Similarly, things like password resets should revoke all existing logins.
Authentication should happen over Ajax. No browser redirection ("hard" redirection) should take place afterwards. All navigation should happen inside the SPA.
Everything should work cross-domain. The SPA will be on www.domain1.com and the server will be on www.domain2.com.
JavaScript should have no access to sensitive data sent by the server, such as session IDs. This is to prevent XSS attacks where malicious scripts can steal the tokens or session IDs from regular cookies, localStorage or sessionStorage.
The ideal mechanism seems to be cookie-based authentication using HttpOnly cookies that contain session IDs. The flow would work like this:
User arrives at a login page and submits their username and password.
The server authenticates the user and sends a session ID as an HttpOnly response cookie.
The SPA then includes this cookie in subsequent XHR requests made to the server. This seems to be possible using the withCredentials option.
When a request is made to a protected endpoint, the server looks for the cookie. If found, it cross-checks that session ID against a database table to make sure the session is still valid.
When the user logs out, the session is deleted from the database. The next time the user arrives on the site, the SPA gets a 401/403 response (since the session has expired), then takes the user to the login screen.
Because the cookie has the HttpOnly flag, JavaScript would not be able to read its contents, so it would be safe from attacks as long as it is transmitted over HTTPS.
Challenges
Here's the specific issue I've run into. My server is configured to handle CORS requests. After authenticating the user, it correctly sends the cookie in the response:
HTTP/1.1 200 OK
server: Cowboy
date: Wed, 15 Mar 2017 22:35:46 GMT
content-length: 59
set-cookie: _myapp_key=SFMyNTYBbQAAABBn; path=/; HttpOnly
content-type: application/json; charset=utf-8
cache-control: max-age=0, private, must-revalidate
x-request-id: qi2q2rtt7mpi9u9c703tp7idmfg4qs6o
access-control-allow-origin: http://localhost:8080
access-control-expose-headers:
access-control-allow-credentials: true
vary: Origin
However, the browser does not save the cookie (when I check Chrome's local cookies it's not there). So when the following code runs:
context.axios.post(LOGIN_URL, creds).then(response => {
context.$router.push("/api/account")
}
And the Account page is created:
created() {
this.axios.get(SERVER_URL + "/api/account/", {withCredentials: true}).then(response => {
//do stuff
}
}
This call does not have the cookie in the header. The server therefore rejects it.
GET /api/account/ HTTP/1.1
Host: localhost:4000
Connection: keep-alive
Accept: application/json
Origin: http://localhost:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
Referer: http://localhost:8080/
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: en-US,en;q=0.8,tr;q=0.6
Do I need to do something special to make sure the browser saves the cookie after receiving it in the login response? Various sources I've read have said that browsers save response cookies only when the user is redirected, but I don't want any "hard" redirects since this is an SPA.
The SPA in question is written in Vue.js, but I guess it applies to all SPAs. I'm wondering how people handle this scenario.
Other stuff I've read on this topic:
SPA best practices for authentication and session management
Are HTTPOnly Cookies submitted via XmlHTTPRequest with withCredentials=True?
Stop using JWT for sessions, part 2: Why your solution doesn't work
I got this working on Vue.js 2 with credentials = true. Setting credentials from client site only half of the story. You need to set response headers from the server as well:
header("Access-Control-Allow-Origin: http://localhost:8080");
header("Access-Control-Allow-Credentials: true");
You can't use wildcard for Access-Control-Allow-Origin like this:
header("Access-Control-Allow-Origin: *");
When you specify the credentials: true header, you are required to specify the orgin.
As you can see, this is a PHP code, you can model it to NodeJS or any server side scripting language you are using.
In VueJS I set credentials = true like this in the main.js:
Vue.http.options.credentials = true
In the component, I successfully logged in using ajax:
<template>
<div id="userprofile">
<h2>User profile</h2>
{{init()}}
</div>
</template>
<script>
export default {
name: 'UserProfile',
methods: {
init: function() {
// loggin in
console.log('Attempting to login');
this.$http.get('https://localhost/api/login.php')
.then(resource => {
// logging response - Notice I don't send any user or password for testing purposes
});
// check the user profile
console.log('Getting user profile');
this.$http.get('https://localhost/api/userprofile.php')
.then(resource => {
console.log(resource.data);
})
}
}
}
</script>
On the server side, things are pretty simple:
Login.php on sets a cookie without making any validation whatsoever (Note: this is done for testing purposes only. You are not advised to use this code in production without validation)
<?php
header("Access-Control-Allow-Origin: http://localhost:8080");
header("Access-Control-Allow-Credentials: true");
$cookie = setcookie('user', 'student', time()+3600, '/', 'localhost', false , true);
if($cookie){
echo "Logged in";
}else{
echo "Can't set a cookie";
}
Finally, the userprofile.php just verifies if a cookie = user is set
<?php
header("Access-Control-Allow-Origin: http://localhost:8080");
header("Access-Control-Allow-Credentials: true");
if(isset($_COOKIE['user'])){
echo "Congratulations the user is ". $_COOKIE['user'];
}else{
echo "Not allowed to access";
}
Successfully logged in
With credentials controls setting and sending cookies so be sure to turn it on when posting to login so the returned cookie is saved in the browser.

Why does Salesforce OAuth2 redirect me from one instance na3 for ex to another na9

I am trying to build a web app that lets the customer add demo data to any Salesforce instance. My demo builder uses OAuth 2 Authorization Code Grant.
I am trying to get the switch instance portion working. However once the user connects to one instance
GET /services/oauth2/authorize?response_type=code&client_id=blabla.UKP&redirect_uri=https%3A%2F%2Fsfblademo.bla.com%2Foauth%2Fcallback HTTP/1.1
Host: na9.salesforce.com
Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_1) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.12 Safari/535.2
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
Cookie: cookie_bla; disco=5:00D50000000Ii39:00550000001ifEp:0|; autocomplete=1; inst=APP5
It redirects to the previous instance. Seems like its reading cookies and redirecting
HTTP/1.1 302 Found
Server:
Location: https://na3.salesforce.com/setup/secur/RemoteAccessAuthorizationPage.apexp?source=blablabla
Content-Type: text/html
Content-Length: 525
Date: Fri, 16 Sep 2011 21:46:58 GMT
The URL has moved here
Is there a way to sign out or clear the cookies salesforce has. I am not running my app on salesforce.
Thanks !
The API logout() call isn't going to work because that will only invalidate the API session and not the UI session stored in the browser cookie on the *.salesforce.com domain, to which your app won't have direct access. That's not to say it isn't still recommended, but to clear that UI cookie, you'll need to redirect the end user to /secur/logout.jsp on the instance_url of the previous session. To make it transparent to end users, you can load it in a hidden iframe like this:
<iframe src='https://{instance_url}/secur/logout.jsp' width='0' height='0' style='display:none;'></iframe>
Before switching to other instance, you can try making the logout call, as described here WS Guide :http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_logout.htm
This will invalidate the previous session hopefully..

Resources