We are trying to connect to a custom Dynamics 365 Finance service operation but are struggling to authorize.
We've set up an app registration
Redirect_uri set to the dynamics url (root)
Enabled implicit grant (both for access tokens and ID tokens)
Single tenant
Assigned the "Dynamics ERP > CustomService.FullAccess" API permission
Assigned the "Dynamics ERP > Odata.FullAccess" API permission
Assigned "Dynamicd ERP > Connector.FullAccess" API permission + granted admin consent for entire AD
Created a secret
We've added the Application (client ID) within the Dynamics 365 environment with a user which has System Administrator role
We can successfully retrieve access tokens both via Postman and .NET (Microsoft.IdentityModel.Clients.ActiveDirectory)
we tried to reach both https://xxxxxxdevaossoap.cloudax.dynamics.com &https://xxxxxxdevaos.cloudax.dynamics.com
Yet when we call custom services, we get a 401 UnAuthorized
A call to Odata also fails for the same reason.
Verify resource in your request for OAuth token (and verify token at https://jwt.io for aud (Audience) field).
It should be same as your primary url without / at the end (like https://d365fo-10-12345678baef10230aos.cloudax.dynamics.com).
Also verify that Azure Active Directory applications (mi=SysAADClientTable) Client Id is equal to appid field in the token (and without any special characters).
You can always inspect Windows Event Log Microsoft-Dynamics-AX-WebApi/Operational
There is good information like this:
Source: Microsoft-Dynamics-AX-WebApi
Category: WebApiOwinConfigurationMissingError
Level: Error
Description: Web API Owin Authentication Configuration Missing Error
infoMessage: Can read the token but failed validating token with exception
'IDX10214: Audience validation failed.
Audiences: 'https://d365fo-10-12345678baef10230aos.cloudax.dynamics.com/'.
Did not match: validationParameters.
ValidAudience: 'null' or validationParameters.ValidAudiences:
'https://d365fo-10-12345678baef10230aos.cloudax.dynamics.com,
00000015-0000-0000-c000-000000000000,
Microsoft.ERP''
Related
I'm trying to figure out how to secure an azure function (service func) that should only be called from another azure function (client func) in the same tenant.
Here's what I've tried in the azure portal:
created a windows function app on a consumption plan
added a hello-world http triggered function with authorization level of "Anonymous"
tested I'm able to call the function anonymously
on the blade for the service func, selected "Authentication"
selected "Add identity provider"
chose "Microsoft"
"Create new app registration"
"Current tenant - Single tenant"
"Require authentication", "HTTP 401"
created a new app registration for the client func, making a note of the client_id and client_secret
I then configured postman to acquire a token from the azure ad (using the only tenant id in play), passing the client_id and client_secret from the app registration of the client func. In the post to the /token endpoint, I gave a grant_type value of "client_credentials".
On posting the request from postman, I received a response containing an access_token. I copied this and pasted into https://jwt.ms to check that all looked ok.
I configured another request in postman to issue a get to the hello-world function. This contained a "Authorization" header with a value of "Bearer " + the access_token received in the previous step.
Despite passing what seems to be a valid access token, the function app (which has no authentication bindings) return a 401 Unauthorized error.
I don't need authorization, just authentication.
Can anyone spot where I went wrong?
I now have this working. I found the following page helpful: Microsoft Doc
Key points:
App roles declared on the app registration of the service app
I didn't manually enter anything into the "Expose an API" page
No authorized client applications listed on the "Expose an API" page
When getting the access token, the scope should be set to the Application ID URI of the target service app registration + "/.default"
API permissions set on the app registration of the client service - these are "Application permissions" NOT "Delegated permissions"
From the function app, select Authentication then edit the identity provider. The value for "Allowed token audiences" needs to be the api://guid value from the "Application ID URI" value of the app registration for the target app service. I noticed that I had problems when using a non-guid value for this
I am having trouble creating a PowerApps Custom Connector which allows me to access Microsoft Graph using APPLICATION permissions.
I have created my Azure AD App Registration, with three API permissions:
Delegated: User.Read
Application: User.Read.All
Application: Directory.Read.All
I have confirmed the Redirect URI is correctly set for Power Apps (https://global.consent.azure-apim.net/redirect) and I have generated a Client Secret.
I have then created TWO different Power Apps custom connectors, and both of them give me exactly the same results.
Connector A: Auth Type: OAuth2.0
Identity Provider: Generic OAuth 2
Client ID & Client Secret (as expected)
Auth URL: https://login.microsoftonline.com/{{Tenant ID}/oauth2/v2.0/authorize
Token URL: https://login.microsoftonline.com/{{Tenant ID}/oauth2/v2.0/token
Scope: https://graph.microsoft.com/.default
Connector B: Auth Type: OAuth 2.0
Identify Provider: Azure Active Directory
Client ID & Client Secret (as expected)
Login URL: https://login.windows.net
Tenant ID: common (this is the default)
Resource URL: https://graph.microsoft.com
Scope: {blank} (this is the default)
I then setup (in both connectors) the same two actions:
/v1.0/me
/v1.0/users
In BOTH connectors the first action (/me) works .. and returns the details of the logged in user.
The second connector (/users) returns "Access Denied - Insufficient Privileges".
This is telling me it is using the Delegated permissions (which allows it to retrieve /me) and not the Application permissions (which is why it can't list all of the user accounts).
So .. is there any setting in Power Apps which allows me to specify that this connector should use Application requests rather than delegated?
Thank you!
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.
Problem: Missing OAuth 2 Refresh Token.
The problem is that the localhost version receives a Refresh Token as part of the granted token but the same code running in GCE does not.
Details:
I have written a Python Flask application that implements Google OAuth 2.0. This web application runs in the cloud with a verified domain name, valid SSL certificate and HTTPS endpoint. This web application unmodified also runs as localhost. The differences between the runtime is that the localhost version does not use TLS. There are no other differences in the code flow.
Other than the Refresh Token is missing and I cannot automatically renew a token, everything works perfectly.
I have researched this issue extensively. API problems such as access_type=offline etc are correctly implemented otherwise I would not get a Refresh Token in the localhost version.
I am using the requests_oauthlib python library.
gcp = OAuth2Session(
app.config['gcp_client_id'],
scope=scope,
redirect_uri=redirect_uri)
# print('Requesting authorization url:', authorization_base_url)
authorization_url, state = gcp.authorization_url(
authorization_base_url,
access_type="offline",
prompt="select_account",
include_granted_scopes='true')
session['oauth_state'] = state
return redirect(authorization_url)
# Next section of code after the browser approves the request
token = gcp.fetch_token(
token_url,
client_secret=app.config['gcp_client_secret'],
authorization_response=request.url)
The token has refresh_token when running in localhost but not when running with in the cloud.
This Google document discusses refresh tokens, which indicates that this is supported for web applications.
Refreshing an access token (offline access)
[Update 11/18/2018]
I found this bug report which gave me a hint to change my code from this:
authorization_url, state = gcp.authorization_url(
authorization_base_url,
access_type="offline",
prompt="select_account",
include_granted_scopes='true')
to this:
authorization_url, state = gcp.authorization_url(
authorization_base_url,
access_type="offline",
prompt="consent",
include_granted_scopes='true')
Now I am receiving the Refresh Token in the public server version and the localhost version.
Next I searched for documentation on the prompt option and found this:
OpenID Conect prompt
prompt (Optional)
A space-delimited list of string values that specifies whether the
authorization server prompts the user for reauthentication and
consent. The possible values are:
none
The authorization server does
not display any authentication or user consent screens; it will return
an error if the user is not already authenticated and has not
pre-configured consent for the requested scopes. You can use none to
check for existing authentication and/or consent.
consent
The authorization server prompts the user for consent before returning
information to the client.
select_account
The authorization server
prompts the user to select a user account. This allows a user who has
multiple accounts at the authorization server to select amongst the
multiple accounts that they may have current sessions for.
If no value is specified and the user has not previously authorized access, then
the user is shown a consent screen.
I think the Google documentation should be updated. On the same page, the following text appears:
access_type (Optional)
The allowed values are offline and online. The
effect is documented in Offline Access; if an access token is being
requested, the client does not receive a refresh token unless offline
is specified.
That statement caused me a lot of confusion trying to debug why I could not obtain a Refresh Token for the public server version but I could for the localhost version.
I have an application registered in Azure AD which uses certificates. I am trying to write a script which would add a new certificate to the application. This can be used to add a new certificate when the existing certificate is going to expire.
I am trying to use AddKey function of Azure AD Graph API. The request body of this api as a parameter 'proof' which is a JWT assertion signed by the existing certificate of the application. The doc says the "aud" claim in JWT should be set to "AAD Graph SPN". Here what is meant by "AAD Graph SPN"?
I tried with a JWT where "aud" was set to "00000002-0000-0000-c000-000000000000". But I am getting the following error,
{
"odata.error": {
"code":"Authorization_RequestDenied",
"message":{
"lang":"en",
"value":"Insufficient privileges to complete the operation."
}
}
}
Any thoughts on this?
I am getting the access token to call the Azure AD Graph API via "Resource Owner Credentials Grant" flow . To get the access token i am using the client_id "1950a258-227b-4e31-a9cf-717495945fc2" (The Well Known Client ID for Azure PowerShell")
My script (For deployment purpose) does something like below,
i) Get the access token as described above and registers a new application in Azure AD with a initial certificate.
ii) When the initial certificate is about to expire it should add a new certificate to the created application.
According to the documentation, you must use a self-signed JWT token to access that API:
As part of the request validation for this service action, a proof of
possession of an existing key is verified before the action can be
performed. The proof is represented by a self-signed JWT token. The
requesting application needs to generate a self-signed JWT token with
the following requirements...
The "Resource Owner Credentials Grant" won't work here in this situation. Instead, use the "Client Credentials Grant":
https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-oauth-service-to-service
The application you want to update should be the Client ID used to get this access token.
The other option is to update the application directly using an PATCH request on the Application Object:
https://msdn.microsoft.com/en-us/library/azure/ad/graph/api/entity-and-complex-type-reference#application-entity
Using this method, you should be able to update using the method you described above (user credentials and an external Client ID)
Let me know if this helps.