Service account authentication with Microsoft Graph API? - microsoft-graph-api

I'm building a server on top of MS Graph API and all I need to do is upload and download images to OneDrive. End users of the application will not access OneDrive directly nor will they all even have accounts. The files need only be accessible to the application itself and a handful of power users who do have their own credentials.
I would like to be able to just configure the credentials for a service account to be the only one accessing the bucket, but it seems all the auth flows require an end user to login. Is there an API-centric way to do this transparently? Or should I infer from the lack of explicit support that this isn't a valid use case for OneDrive and I should look at Azure Blob Storage?

You can authenticate to MS Graph API with username + password using the class UserPasswordCredential (https://learn.microsoft.com/en-us/dotnet/api/microsoft.identitymodel.clients.activedirectory.userpasswordcredential?view=azure-dotnet). Sample:
UserCredential creds = new UserPasswordCredential("fred#contoso.onmicrosoft.com","password");
var bearerToken = AcquireToken("https://graph.microsoft.com", "d1ddf0e4-d672-4dae-b554-9d5bdfd93547", creds).Result;
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + result.AccessToken);
var graphInfo = client.GetAsync("https://graph.microsoft.com/v1.0/me/").Result;
Warning: There are only few use cases where using username + password directly is legitimate (see http://www.cloudidentity.com/blog/2014/07/08/using-adal-net-to-authenticate-users-via-usernamepassword/). Additionally, UserPasswordCredential is not available in DotNetCore.

Related

Get Azure AD directory users in a Rails app

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.

How do I authenticate against the Dynamics 365 Data Export Service API?

I've set up something called the Data Export Service for Dynamics 365 so that it replicates into an Azure SQL database. This is working as expected.
I'm trying to find a way to be proactively notified if this service encounters any errors. There does not appear to be a native way to do this through the setup in CRM itself, but they do provide an API. The Swagger page outlining all methods can be found here.
I'm trying to call the GetProfilesByOrganizationId method using Postman:
https://discovery.crmreplication.azure.net/crm/exporter/profiles?organizationId=4ef7XXXX-XXXX-XXXX-XXXX-XXXXXX8a98f&status=true
I'm having issues with authentication and always receive the following error:
"Message": "Received unauthenticated requestRequest Url https://discovery.crmreplication.azure.net/crm/exporter/profiles?organizationId=4ef7XXXX-XXXX-XXXX-XXXX-XXXXXX8a98f&status=true"
I have registered an application in Azure that has permission to access Dynamics 365 on behalf of the authenticated user which in this case is me, the administrator.
I have set the Type to OAuth 2.0 on the Authorization tab of Postman. I have requested an Access Token using the Grant Type of Authorization Code against the above application successfully. This has added a header to the request:
Key: Authorization
Value: Bearer BIGLONGACCESSTOKEN
Despite this header being present I still get the error mentioned above.
The API documentation implies the authentication is OAuth2 Implicit Grant Flow (click on any red exclamation mark in the documentation) but I can't get this to work in Postman. When I try to request a token with this method I get the error:
unsupported_response_type
... in the Postman console.
Any ideas how to authenticate (with Implicit Grant?) against this API in Postman?
(I'd accept C# examples if they're more appropriate, but I'd be surprised if Postman can't show me what I need)
It looks like the code sample shown by Microsoft can work if updated with newer methods and with some extra configuration in Azure that's not documented.
Azure configuration
By installing the Data Export service (and assuming it's all working) you'll have a new Enterprise Application listed in Azure AD as Crm Exporter.
To take advantage of this application and authenticate with the Data Export API you must configure an app of your own.
Go to the App registrations tab in Azure AD and add a new application registration.
Give it a name and set the Application type to Native. The redirect URI doesn't typically matter as long as it's valid.
Click the Manifest button to edit the manifest, change the property oauth2AllowImplicitFlow to true and save the changes.
The only other important configuration is Required permissions which should be set as below:
Windows Azure Active Directory
Delegated permissions
Sign in and read user profile
Data Export Service for Microsoft Dynamics 365 (Crm Exporter)
Delegated permissions
Have access to Data Export Service for Microsoft Dynamics 365 API
You will then need to click Grant Permissions.
C# changes
The updated method looks like this:
using Microsoft.IdentityModel.Clients.ActiveDirectory;
string clientId = "11cfXXXX-XXXX-XXXX-XXXX-XXXXXXXXd020";
string user = "my.username#domain.com";
string password = "PASSWORD";
var authParam= await AuthenticationParameters.CreateFromResourceUrlAsync(
new Uri("https://discovery.crmreplication.azure.net/crm/exporter/aad/challenge")
);
var context = new AuthenticationContext(authParam.Authority, false);
var credentials = new UserPasswordCredential(user, password);
var token = await context.AcquireTokenAsync(authParam.Resource, clientId, credentials).AccessToken;
You can now query the Data Export API by providing the token as a header:
Authorization : Bearer eJ0y........Hgzk
curl -X GET --header 'Accept: application/json' 'https://discovery.crmreplication.azure.net/crm/exporter/profiles?organizationId=MyOrgId&status=true'

Microsoft Graph API returning 403 to any request

I'm working on an application that, in this point, will retrieve the Office Groups that the logged in user is included and perform actions based on that info.
I'm using oAuth2.0 and the v2.0 token endpoint to get access without a user, and with the code below, I can provide administrator consent to the permissions (which were applied to the application permissions on the new Application Registration Portal https://apps.dev.microsoft.com/ and appear on the Enterprise Applications section on Azure), request the token to Azure and receive it, but even with the permissions applied and that token, I get a 403 response code (Insufficient privileges) from the Graph API to any request I try to perform.
The code for those actions is the following:
// Request Admin Consent
HttpRequestMessage adminConsentRequest = new HttpRequestMessage(HttpMethod.Get, "https://login.microsoftonline.com/" + TenantId + "/adminconsent?client_id="+ClientId+"&redirect_uri=https%3A%2F%2Flocalhost%3A44369%2FHome%2F");
var adminConsentResponse = await client.SendAsync(adminConsentRequest);
// Request Token
HttpRequestMessage tokenRequest = new HttpRequestMessage(HttpMethod.Post, "https://login.microsoftonline.com/"+TenantId+"/oauth2/v2.0/token") { Content = new FormUrlEncodedContent(tokenRequestPairs) };
var tokenResponse = await client.SendAsync(tokenRequest);
string tokenResponseBody = await tokenResponse.Content.ReadAsStringAsync();
var deserializedTokenResponse = (JObject)JsonConvert.DeserializeObject(tokenResponseBody);
string accessToken = deserializedTokenResponse["access_token"].Value<string>();
// Call Microsoft Graph API
HttpRequestMessage graphRequest = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/me/memberOf");
graphRequest.Headers.Add("Authorization", "Bearer "+accessToken);
var graphResponse = await client.SendAsync(graphRequest);
string graphResponseBody = await graphResponse.Content.ReadAsStringAsync();
var deserializedGraphResponse = (JObject)JsonConvert.DeserializeObject(graphResponseBody);
Enterprise Application permissions on Azure
APP Registration Portal permissions
Can someone guide to any kind of mistake I'm making?
With the authorization token and the permissions applied, I can't see why would I get an AccessDenied response.
It's been more than 48 hours since I applied the permissions, so it's not a sync problem.
Update: So thanks to #juunas I managed to reapply the permissions and the token now shows all the permissions applied on the Application Portal (User.Read.All, Directory.Read.All and Group.Read.All), but the API still returns 403 status code (Authorization_RequestDenied).
I've tried another endpoint without the /me just to make sure that is not a reference problem, but it also returns 403 status code.
One thing that is funny is that the App was registered on the new app portal as I said, and it appears on Enterprise Applications on Azure, but not on my App Registrations, so I can only alter permissions on the new App Portal. It should be like this, since I'm using a new registration portal?
After a discussion in the comments, the problem was fixed by re-consenting the permissions similarly as shown in my blog post: https://joonasw.net/view/the-grant-requires-admin-permission (though it is written for v1).
To run admin consent again, you need to add prompt=admin_consent to the authorize URL.
Okay, so a few minutes after the update on the original post, the token was accepted by the endpoints.
The only problem is that the graph API does not recognize the ID of the user logged in to use the /me endpoints, but I bypassed that using the /{group-id}/members endpoint (in my case, it's not how I wanted but solves my problem).
Thanks #juunas for the help!

Azure AD App Registration - Exchange API - Read Calendar

I have the following permissions granted in my Azure AD App:
App:
Read calendars in all mailboxes
Read and write calendars in all mailboxes
Read calendars in all mailboxes
Delegated:
Read user and shared calendars
Read and write user and shared calendars
Read and write user calendars
Read user calendars
Read and write user and shared calendars
Read user and shared calendars
Registration Screen Shot
I am successfully generating an Access Token like the following:
const string clientId = "my-client-id";
const string clientSecret = "my-secret"; // C
var tenant = "mytenant.onmicrosoft.com";
var authContext = new AuthenticationContext($"https://login.windows.net/{tenant}/oauth2/token");
var credentials = new ClientCredential(clientId, clientSecret);
AuthenticationResult authResult = await authContext.AcquireTokenAsync("https://graph.microsoft.com/", credentials);
With that Access Token, I am trying to make a basic request to /CalendarView passing the Bearer token header:
https://outlook.office.com/api/v2.0/users/my#email.com/calendarview?startDateTime=2017-11-12&endDateTime=2017-11-13
However, I keep receiving Access Denied. Are there additional permissions I need to set? Am I calling into the correct endpoint?
You don't include the body of the error response, but my guess is that you're hitting this because Exchange won't accept a token generated with a shared secret. Instead, you need to use a certificate-based assertion to request the token. Azure documents this a "Second case: Access token request with a certificate" here.
I actually was able to figure this out. Instead of using the Exchange API, I just applied the permissions in the Graph API.
Hit the following endpoint:
https://graph.microsoft.com/v1.0/users/{email}/calendarview?startDateTime={startDate}&endDateTime={endDate}
It's not very clear the difference between which API to use... but I'm moving forward now.

Authenticate to Dynamics 365 On premise

I am trying connect to Dynamics 365 On-premise with the OData client for .net
I tried to authenticate through basic authentication, however this is not working.
var c = new Microsoft.Dynamics.CRM.System(new Uri("https://mycrm01/crm/api/data/v8.2/"));
c.SendingRequest2 += (o, requestEventArgs) => {
var creds = username + ":" + password;
var encodedCreds = Convert.ToBase64String(Encoding.ASCII.GetBytes(creds));
requestEventArgs.RequestMessage.SetHeader("Authentication", "Basic" + encodedCreds);
};
var contacts = c.Contacts.Where(x => x.Firstname=="testuser");
foreach (var contact in contacts)
{
}
The error I recieve is: HTTP Error 401 - Unauthorized: Access is denied
Can someone help me how this is done?
In general I only use the OData client from JavaScript. When using .NET, I use the SDK libraries that provide authentication and access via the CrmServiceClient class.
To use the OData client from C#, this article outlines the various authentication methods: https://msdn.microsoft.com/en-us/library/mt595798.aspx
Web API authentication patterns
There are three different ways to manage authentication when using the
Web API. With JavaScript in web resources
When you use the Web API with JavaScript within HTML web resources,
form scripts, or ribbon commands you don’t need to include any code
for authentication. In each of these cases the user is already
authenticated by the application and authentication is managed by the
application. With on-premises deployments
When you use the Web API for on-premises deployments you must include
the user’s network credentials. The following example is a C# function
that will return an HttpClient configured for a given user’s network
credentials: C#
private HttpClient getNewHttpClient(string userName,string
password,string domainName, string webAPIBaseAddress) {
HttpClient client = new HttpClient(new HttpClientHandler() { Credentials = new NetworkCredential(userName, password, domainName)
});
client.BaseAddress = new Uri(webAPIBaseAddress);
client.Timeout = new TimeSpan(0, 2, 0);
return client;
}
With Microsoft Dynamics 365 (online) or internet facing deployments
When you use the Web API for Dynamics 365 (online) or an on-premises
Internet-facing deployment (IFD) you must use OAuth as described in
Connect to Microsoft Dynamics 365 web services using OAuth.
If you’re creating a single page application (SPA) using JavaScript
you can use the adal.js library as described in Use OAuth with
Cross-Origin Resource Sharing to connect a Single Page Application to
Microsoft Dynamics 365.

Resources