I'm trying to set up programmatic access to a corporate Sharepoint Online site from a desktop app. My org has an Office 365 subscription.
I've set up a registered app in the Azure portal, granted the AllSites.Write permission:
Now, I'm trying to navigate to the following URL in a browser control:
URL = "https://login.microsoftonline.com/" + Tenant + "/oauth2/v2.0/authorize?" +
"client_id=" & ClientID +
"&response_type=code" +
"&redirect_uri=foo%3A%2F%2Fback" +
"&response_mode=query" +
"&scope=user.read%20allsites.write"
As soon as I do that, there's no UI, I immediately get a callback with the following error message:
AADSTS650053: The application 'MyApp' asked for scope 'allsites.write' that doesn't exist on the resource '00000003-0000-0000-c000-000000000000'. Contact the app vendor.
The scope is right there. What am I missing?
The same logon flow worked with a slightly different scope (Sites.ReadWrite.All), but then I found out that the Graph API didn't expose SharePoint list item attachments and decided to fall back to the legacy SharePoint REST API. Changed the scopes on the portal, changed the scopes in the logon URL, got the error.
The stale logon session couldn't get in the way, could it? Normally, when scopes change, the OAuth UI pops the permissions window, doesn't it?
Got past this error, but now Sharepoint Online doesn't accept my token, says "Invalid issuer or signature". For the record, when I provide the scope https://microsoft.sharepoint-df.com/AllSites.Write or https://{mytenant}.sharepoint.com/AllSites.Write, the logon flow works.
UPDATE: accepts now. For that, I had to use the older version of the OAuth endpoints: https://login.microsoftonline.com/{tenant}/oauth2/authorize for initiation of the logon flow, and https://login.windows.net/{tenant}/oauth2/token?api-version=1.0 to exchange the code for the token. The latter takes the resource parameter, which is to be https://{tenant}.sharepoint.com.
The endpoint at https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token, which is specified all over the Graph docs, doesn't take the resource parameter, which seems to be essential here (or maybe it's called something else).
Related
I want to send a request from a Controller of the ASP.NET MVC application that is deployed on the Microsoft Azure Cloud Active Directory and receive a response from the service that is still deployed on the Microsoft Azure Cloud Active Directory.
For this purpose, I downloaded an example you can see from here and customize it for myself. A detailed document of my actions is contained in the same link.
When I tested service and web applications on my azure portal, I encountered an error message in the header:
Failed to acquire token silently as no token was found in the cache.
Call method AcquireToken
Where the error occurred is the following part in my controller:
ClientCredential credential = new ClientCredential( clientId, appKey );
result = await authContext.AcquireTokenSilentAsync( todoListResourceId, credential, new UserIdentifier( userObjectID, UserIdentifierType.UniqueId ) );
clientId: Identifier of my web application installed on Azure AD (For example: c95d45dd-ba7f-41be-a995-1db604afff32)
appKey: Hidden key value of my web application in the portal
todoListResourceId: Identification of my API application installed on Azure AD (For example: 4cfebcb4-6f2e-4eeb-84f2-4220f65774ed)
userObjectID: Value returned from the following piece of code
string userObjectID = ClaimsPrincipal.Current.FindFirst( "http://schemas.microsoft.com/identity/claims/objectidentifier" ).Value;
i.e. a value for the user who is online in the browser. As stated in the document on my GitHub link, this value is not my Microsoft account that I used when logging into my azure portal, but a value for my user that I registered to my Azure Active Directory
A similar topic to this topic has been discussed and answered here before, but this answer has not solved my problem.
I've been working for days, but I haven't gotten a response from the GET, POST, PUT, DELETE methods in the service. I keep dealing with the error in the title. I'm waiting for your help.
The reason you're receiving this error is because the call acquiretokensilentasync is EXPECTED to throw that error when the cache is empty. This call is meant to be caught in a try catch. If it does throw this error it should call the acquiretokenasync call.
In addition to that it looks like you're trying to utilize the client credential flow with the acquiretokensilentasync call, which is not the right method to use per the ADAL wiki docs.
See here on how to do this properly: https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/wiki/Client-credential-flows
It looks like you're using an app id and secret, the method in particular on how to do this per the doc linked above is :
AuthenticationContext authenticationContext = new AuthenticationContext("https://login.microsoftonline.com/<tenantId>");
AuthenticationResult result = await authenticationContext.AcquireTokenAsync("https://resourceUrl", clientCredential);
More documentation specifically for the acquiretokensilentasync call can be found here : https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/wiki/AcquireTokenSilentAsync-using-a-cached-token
From the doc above :
Recommended pattern to acquire a token
Now that you have seen both AcquireTokenAsync, AcquireTokenSilentAsync, it's the right moment to
present the recommended usage pattern for calling these methods. The
idea is that you want to minimize the number of signings for the user,
and therefore you'd want to:
first try to acquire a token silently, and if this call fails you try
to get one interactively. Note that, AcquireTokenSilent does not need
to be called in the Client credentials flow (when the application
acquires token without a user, but in its own name)
Note that AcquireTokenSilent can fail for several reasons, such as the
cache does not contain a token for the user, or the token has expired
and cannot be refreshed. For these reasons, a call to
AcquireTokenAsync will usually get a token. But there are also issues
such as network problems, STS unavailability, etc., which won't be
directly solvable. You will see them in more details in the article
about best practices for Handling errors.
In addition to that, it looks like you're using the ADAL Library, I suggest to move over to the MSAL library since Microsoft is slowly moving towards utilizing the MSAL libraries and will at some point in the future (maybe far future) move off of ADAL/V1.0 endpoint. There are no current hard dates for this however. The doc on moving over from ADAL to MSAL can be found here :
https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/Adal-to-Msal
I have a Rails 6 application and I want to use Azure Active Directory as an authentication system (with open id connect, saml2 and ldap).
The authentication is done.
Now I am trying to display user information like names or email addresses. I also want to be able to export all users of a directory.
I have tried to set a configuration up like so:
In my Rails app, in the admin panel, an admin can configure Azure AD for my application
in the config, the admin copies and pastes the configuration link provided by Azure AD (a JSON response)
Then, copies and pastes the app client_id
Then, the tenant_id (directory id)
Here is a piece of code that I expected to work:
def update_oidc
identity_provider = IdentityProvider.find_by(provider_type: 'open_id_connect', id: params[:id])
client_id = params[:client_id].strip
metadata_url = params[:metadata_url].strip
tenant_id = params[:tenant_id].strip
metadata = HTTParty.get(metadata_url).parsed_response
identity_provider.update(config: {
metadata: metadata,
metadata_url: metadata_url,
client_id: client_id,
tenant_id: tenant_id,
})
if tenant_id
directory_access_url = "https://graph.windows.net/#{tenant_id}/users?api-version=1.6"
result = HTTParty.get(directory_access_url).parsed_response
identity_provider.directories.find_or_create_by(tenant_id: tenant_id).update(
business_phones: result["business_phones"],
display_name: result["display_name"],
given_name: result["given_name"],
job_title: result["job_title"],
email: result["user_principal_name"],
mobile_phone: result["mobile_phone"],
office_location: result["office_location"],
surname: result["surname"]
)
end
redirect_to identity_provider
end
As the tenant_id is the directory id, i thought that we might be able to access user info this way (and following the Microsoft Docs). The thing is, it doesn't work because even though I'm connected to my Azure AD directory in my app, when I run result = HTTParty.get(directory_access_url).parsed_response, i have an authentication error telling me the token has expired or that i need to be connected.
I don't want to use PowerShell or anything like this. I want to be able to access directories data through my app.
Can someone tell me what i'm doing wrong or come up with an idea ?
Thanks
Just according to your code, I think you want to get the collection of users via the Azure AD Graph REST API Get users using jnunemaker/httparty library.
However, it seems to be missing the required header Authorization with its value like Bearer eyJ0eX ... FWSXfwtQ as the section Authentication and authorization of the offical document Operations overview | Graph API concepts said. Meanwhile, you have done the authentication with OpenID Connect, but Azure AD Graph API requires the access token as Authorization value from OAuth2 as the content below said.
The Graph API performs authorization based on OAuth 2.0 permission scopes present in the token. For more information about the permission scopes that the Graph API exposes, see Graph API Permission Scopes.
In order for your app to authenticate with Azure AD and call the Graph API, you must add it to your tenant and configure it to require permissions (OAuth 2.0 permission scopes) for Windows Azure Active Directory. For information about adding and configuring an app, see Integrating Applications with Azure Active Directory.
Azure AD uses the OAuth 2.0 authentication protocol. You can learn more about OAuth 2.0 in Azure AD, including supported flows and access tokens in OAuth 2.0 in Azure AD.
So I'm afraid you have to get the access token manually via OAuth2 for Azure AD again for using Graph API, or just simply refer to the sample code samples/authorization_code_example/web_app.rb using the adal library of GitHub repo AzureAD/azure-activedirectory-library-for-ruby for Ruby.
I have an identity server built with Identity server 4. There is one main API with several angular web applications build by third party customers that access this API endpoint.
Now I would like to create a second API but its only for internal use with OUR official plugin. I am trying to figure out how to lock it down so that only our app can access it. I am not a fan security by obscurity and assuming that the third party's dont know its there so wont try and access it.
My first thought was to add a new scope for this API but by doing that its going to popup and ask the users for access to the data which isnt really need.
The only thing i can think of would be to check the client id some how in the API and add a policy for it. This really isnt right ether as to my understanding polciy should be checking stats of the user and not the client itself.
services.AddAuthorization(
options => {
options.AddPolicy("DevConsole", policy => IsClientId(xxxx)
}
);
Is it possible to lock down an API based upon a single client id? or am i going at this in the wrong way.
Another idea i had was to add another claim if they login with this client id which seams like overkill to me.
Example:
Lets say that I have an API endpoint that allows you to update the usersname. All users have access to their name this isnt a scope issue. However only our official app has access to update the usersname. Any app created by third party developers do not have access to the endpoint to update a usersname.
So our official plugin has a client id of 123 and yours has a client id of 321. A user logged though client id 321 can not access this endpoint. User logged in though client id 123 can.
I am starting to think this isnt possible because Oauth and Openid are completely user based. There is no way to validate the user based upon the client they authenticated with.
if I understand the problem correctly, I would create a new Client on Identity Server, for the "main API" and a new Resource for the "internal API"
This would allow the "main API" to also be a client, with client credentials grant type, therefore it has a id+secret and is allowed to request a token for itself. In this case, you will now request the newly created scope for in "internal API" and the users will have no knowledge that this entity evens exists.
After going back and forth with this with this it occurred to me that the client id is returned as a claim. So when i got in this morning i checked.
This should enable me to add a policy for only our official plugin.
services.AddAuthorization(options =>
{
options.AddPolicy("IsOfficalApp", policy => IsCheckOfficalClient());
});
This should enable me to lock down the API endpoints in question without requiring additional authorization from the users.
Previously asked this question in the Salesforce StackExchange which they considered off-topic so asking here to see if I can get an answer.
Background
I am attempting to use the immediate parameter to check if a Salesforce user has already approved access when going through the Web Server OAuth Flow as documented on OAuth 2.0 Web Server Authentication Flow. My reasoning for this is that I do not want the login or consent prompts to appear so I can reject access if they have not already approved.
Once the callback page is hit, I am always receiving the parameter error=immediate_unsuccessful even if the user has approved the application before and is logged in.
I have attempted to check this via a customised Google OAuth 2 Playground and setting immediate=true or immediate=false to the end of the authorize endpoint. On =false, the consent prompt shows and then you can grant access. On =true, this returns the same error as listed previously.
The Connected App that has been set up has api and refresh_token as the available scopes, users are able to authorize themselves and there are no ip restrictions set. The client id and secret from this app is then passed into the OAuth 2 Playground.
Below is a brief example on how my proper application redirects to the auth url using Java and the Google OAuth client library. We initially authorize the client without the immediate and then later on call the same code with immediate=true (shown in example)
AuthorizationCodeFlow authorizationCodeFlow = new AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
httpTransport,
GsonFactory.getDefaultInstance(),
new GenericUrl("https://login.salesforce.com/services/oauth2/token"),
new ClientParametersAuthentication(CLIENT_ID, CLIENT_SECRET),
CLIENT_ID,
"https://login.salesforce.com/services/oauth2/authorize")
.setCredentialDataStore(StoredCredential.getDefaultDataStore(MemoryDataStoreFactory.getDefaultInstance()))
.build();
AuthorizationCodeRequestUrl authUrl = authorizationCodeFlow.newAuthorizationUrl()
.setRedirectUri("https://72hrn138.ngrok.io/oauth/callback")
.setScopes(ImmutableSet.<String> of("api", "refresh_token"))
.set("prompt", "consent")
.set("immediate", "true");
response.redirect(authUrl);
Question(s)
Are there any settings that I may have missed in Salesforce that would alleviate the error?
Is there any other option in the OAuth 2 spec that has to be set for the immediate option to work?
Does the immediate setting work?
I managed to solve this issue in the end. To allow the immediate=true option to work, the scopes have to be removed from the request. In the example provided you would amend the authUrl to the following:
AuthorizationCodeRequestUrl authUrl = authorizationCodeFlow.newAuthorizationUrl()
.setRedirectUri("https://72hrn138.ngrok.io/oauth/callback")
.set("prompt", "consent")
.set("immediate", "true");
I believe the theory is that defining a scope means you are asking for permissions to use those scope and therefore requires approval for those permissions. This clashes with the immediate option which states that the user must be logged in and the client id already been approved for it to succeed.
I have been successfully using Google API (via HTTP/REST, as well as using the .NET client library) with a Google Service Account to access the files in Google Drive.
Recently, I am exploring the Fusion Tables. I am able to use the API with user authorization via a web application. However, when I try to access it using Google Service Account under the same project, it failed with the below error, whenever I have https://www.googleapis.com/auth/fusiontables in the scope:
https:// www.googleapis.com/oauth2/v3/token
HTTP 401
{"error": "unauthorized_client", "error_description": "Unauthorized client or scope in request." }
The error goes away, when I remove https:// www.googleapis.com/auth/fusiontables and the same code block works fine with https://www.googleapis.com/auth/drive and other scopes.
I have checked and confirmed the "Fusion Tables API" is already enabled for my project at Google Developers Console. (Otherwise, my user authorization via a web application would not be working at the first place.)
Is there anything which I could have missed out? Any help would be greatly appreciated.
I just come across this:
Google drive service account and "Unauthorized client or scope in request"
Even though it does not seems to be related at the first glance, it is indeed the same issue.
Problem resolved after removing User = svcAcct, from the below code block.
ServiceAccountCredential credential;
credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(svcAcct) {
// User = svcAcct, *** removed ***
Scopes = new System.Collections.Generic.List<string>(scopes.Split(' '))
}.FromCertificate(certificate)
);
Hence, here is the general advise:
DO NOT call ServiceAccountCredential.Initializer with User = svcAcct unnecessarily.
Only do this when you are trying to impersonating a difference user
(with the condition that the appropriate setup has been correctly done
in Google Apps Admin Console).
Even though it may not produce any error under certain cases, there
are some unexpected behaviors when including an email address of the
service account itself in the JWT claim set as the value of the "sub"
field.