Call SharePoint online REST API from azure API - access-token

I have developed a Azure API protected with AAD , which is working fine,
now I would like to call SharePoint online REST API from my Azure API ,my SharePoint online is using same active directory as my azure API.
For calling SP API i need access token to authenticate SharePoint
I assume the access token which authenticated the Azure API would be same to call SharePoint API
this is what I did :
My Azure API is registered in azure active directory (which is automatically done which I made authentication on )
I updated the Azure app's manifest.json to enable oauth2 implicit flow:
"oauth2AllowImplicitFlow": true
I granted the app access to "Read and write items and lists in all site collections" on behalf of the user (under delegated permissions) from the Azure AD app settings page ("permissions to other applications").
I tried to this code to get access token :
string clientId = "xxxxxxxxxx";
string appKey = xxxxxxxxxxx";
string aadInstance = "https://login.microsoftonline.com";
string tenant = "mydomain.onmicrosoft.com";
string domain = "mydomain.onmicrosoft.com";
string resource = "https://mydomain.sharepoint.com";
AuthenticationResult result = null;
ClientCredential clientCred = new ClientCredential(clientId, appKey);
string authHeader = HttpContext.Current.Request.Headers["Authorization"];
string userAccessToken = authHeader.Substring(authHeader.LastIndexOf(' ')).Trim();
UserAssertion userAssertion = new UserAssertion(userAccessToken);
string authority = aadInstance + domain;
AuthenticationContext authContext = new AuthenticationContext(authority);
//result = await authContext.AcquireTokenAsync(resource, clientCred); // auth without user assertion (fails, app only not allowed)
result = await authContext.AcquireTokenAsync(resource, clientCred, userAssertion); // clientCred and userAssertion params have swapped places since Kirk's blog
return result.AccessToken;
but authHeader is null ,
I came across this question which mentions in order to retrieve a user token uses ADAL.js using authenticationContext.acquireToken(clientId), then include the resulting token in the header of the AJAX request to the WebAPI
I am not sure how i need to include this in my azure API
anyway i appreciate any thoughts or idea to get access token to run SharePoint online APIs behalf of logged in user in azure API

Related

Application Permission support for Dynamics Customer Engagement Web API

We are planning to move from Organization Service to Common Data Service Web API so we could utilize OAuth 2.0 authentication instead of a service account which customer has some security concerns.
Once we did some prototype, we discovered that the Web API authentication is a little different from typical Graph API authentication. It only supports Delegated Permission. Thus a user credential must be presented for acquiring the access token.
Here is the Azure AD Graph API permission for CRM Web API:
Here is the code in acquiring the access token for the sample code at Web API Global Discovery Service Sample (C#)
string GlobalDiscoUrl = "https://globaldisco.crm.dynamics.com/";
AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com", false);
UserCredential cred = new UserCredential(username, password);
AuthenticationResult authResult = authContext.AcquireToken(GlobalDiscoUrl, clientId, cred);
Here is another similar post Connect to Dynamics 365 Customer Engagement web services using OAuth although it is more than one year old.
Do you know when MS would support Application permission to completely eliminate the user from authentication? Or there is any particular reason to keep the user here. Thanks for any insights.
[Update 1]
With below answer from James, I did the modification for the code, here is my code
string clientId = "3f4b24d8-61b4-47df-8efc-1232a72c8817";
string secret = "xxxxx";
ClientCredential cred = new ClientCredential(clientId, secret);
string GlobalDiscoUrl = "https://globaldisco.crm.dynamics.com/";
AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com/common", false);
AuthenticationResult authResult = authContext.AcquireToken(GlobalDiscoUrl, cred);
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
client.Timeout = new TimeSpan(0, 2, 0);
client.BaseAddress = new Uri(GlobalDiscoUrl);
HttpResponseMessage response = client.GetAsync("api/discovery/v1.0/Instances", HttpCompletionOption.ResponseHeadersRead).Result;
if (response.IsSuccessStatusCode)
{
//Get the response content and parse it.
string result = response.Content.ReadAsStringAsync().Result;
JObject body = JObject.Parse(result);
JArray values = (JArray)body.GetValue("value");
if (!values.HasValues)
{
return new List<Instance>();
}
return JsonConvert.DeserializeObject<List<Instance>>(values.ToString());
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
so I am able to acquire the access token, but it still could not access the global discovery services.
Here is what the access token looks like:
{
"aud": "https://globaldisco.crm.dynamics.com/",
"iss": "https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/",
"iat": 1565802457,
"nbf": 1565802457,
"exp": 1565806357,
"aio": "42FgYEj59uDNtwvxTLnprU0NYt49AA==",
"appid": "3f4b24d8-61b4-47df-8efc-1232a72c8817",
"appidacr": "1",
"idp": "https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/",
"tid": "f8cdef31-a31e-4b4a-93e4-5f571e91255a",
"uti": "w8uwKBSPM0y7tdsfXtAgAA",
"ver": "1.0"
}
By the way, we did already create the application user inside CRM by following the instruction.
Anything I am missing here?
[Update 2]
For WhoAmI request, there are different results. If I am using latest MSAL and with authority "https://login.microsoftonline.com/AzureADDirectoryID/oauth2/authorize", I would be able to get the correct result. If I am using MSAL with "https://login.microsoftonline.com/common/oauth2/authorize", it won't work, I would get unauthorized error. If I am using ADAL 2.29, it is not working for both authority. Here is the working code:
IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create("3f4b24d8-61b4-47df-8efc-1232a72cxxxx")
.WithClientSecret("xxxxxx")
// .WithAuthority("https://login.microsoftonline.com/common/oauth2/authorize", false)
.WithAuthority("https://login.microsoftonline.com/3a984a19-7f55-4ea3-a422-2d8771067f87/oauth2/authorize", false)
.Build();
var authResult = app.AcquireTokenForClient(new String[] { "https://crmxxxxx.crm5.dynamics.com/.default" }).ExecuteAsync().Result;
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
client.Timeout = new TimeSpan(0, 2, 0);
client.BaseAddress = new Uri("https://crm525842.api.crm5.dynamics.com/");
HttpResponseMessage response = client.GetAsync("api/data/v9.1/WhoAmI()", HttpCompletionOption.ResponseHeadersRead).Result;
if (response.IsSuccessStatusCode)
{
//Get the response content.
string result = response.Content.ReadAsStringAsync().Result;
Console.WriteLine(result);
}
else
{
throw new Exception(response.ReasonPhrase);
}
The documentation isn't the easiest to follow, but from what I understand you should start with Use OAuth with Common Data Service.
You then have two subtle options when registering your app. The second does not require the Access Dynamics 365/Common Data Service as organization users permission
Giving access to Common Data Service
If your app will be a client which allows the authenticated user to
perform operations, you must configure the application to have the
Access Dynamics 365 as organization users delegated permission.
Or
If your app will use Server-to-Server (S2S) authentication, this step
is not required. That configuration requires a specific system user
and the operations will be performed by that user account rather than
any user that must be authenticated.
This is elaborated further.
Connect as an app
Some apps you will create are not intended to be run interactively by
a user. ... In these cases you can create a special application user
which is bound to an Azure Active Directory registered application and
use either a key secret configured for the app or upload a X.509
certificate. Another benefit of this approach is that it doesn't
consume a paid license.
Register your app
When registering an app you follow many of the same steps ... with the
following exceptions:
You do not need to grant the Access Dynamics 365 as organization users permission.
You will still have a system user record in Dynamics to represent the application registration. This supports a range of basic Dynamics behaviours and allows you to apply Dynamics security to you app.
As opposed to a username and password you can then use the secret to connect.
string serviceUrl = "https://yourorg.crm.dynamics.com";
string clientId = "<your app id>";
string secret = "<your app secret>";
AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com/common", false);
ClientCredential credential = new ClientCredential(clientId, secret);
AuthenticationResult result = authContext.AcquireToken(serviceUrl, credential);
string accessToken = result.AccessToken;
Or a certificate.
string CertThumbPrintId = "DC6C689022C905EA5F812B51F1574ED10F256FF6";
string AppID = "545ce4df-95a6-4115-ac2f-e8e5546e79af";
string InstanceUri = "https://yourorg.crm.dynamics.com";
string ConnectionStr = $#"AuthType=Certificate;
SkipDiscovery=true;url={InstanceUri};
thumbprint={CertThumbPrintId};
ClientId={AppID};
RequireNewInstance=true";
using (CrmServiceClient svc = new CrmServiceClient(ConnectionStr))
{
if (svc.IsReady)
{
...
}
}
You may also want to check out Build web applications using Server-to-Server (S2S) authentication which appears to be a similar (but different).
Use server-to-server (S2S) authentication to securely and seamlessly
communicate with Common Data Service with your web applications and
services. S2S authentication is the common way that apps registered on
Microsoft AppSource use to access the Common Data Service data of
their subscribers. ... Rather than user credentials, the application is authenticated based on a service principal identified by an Azure AD Object ID value which is stored in the application user record.
Aside; if you are currently using the Organization Service .NET object, that is being migrated to using the Web API internally.
Microsoft Dynamics CRM 2011 endpoint
The Dynamics 365 SDK assemblies will be updated to use the Web API.
This update will be fully transparent to you and any code written
using the SDK itself will be supported.

Login to web server with Google Sign-in

I'm connecting to a 3rd party web server from an HTTP client (Java or Dart - Android app) to download some resources (XML or IMG files) that belong to the current user on that server. This site requires login with Google Sing-In. I have everything set up in my Android app to login the user with Google, I obtained their authorization idToken. But how do actually use it in HTTP GET or POST methods to download the protected resources?
With BASIC authentication it's easy - just set HTTP 'Authorization' header correctly ("Basic " + user:password encoded as base64), call GET, and I download the desired resource. But I cannot find any information on how to do this with Google Sing-In. Do I send the idToken I received from Google in some headers? What other magic is needed?
Adding a Java code snippet, hope it helps:
// (Receive authCode via HTTPS POST)
if (request.getHeader('X-Requested-With') == null) {
// Without the `X-Requested-With` header, this request could be forged. Aborts.
}
// Set path to the Web application client_secret_*.json file you downloaded from the
// Google API Console: https://console.developers.google.com/apis/credentials
// You can also find your Web application client ID and client secret from the
// console and specify them directly when you create the GoogleAuthorizationCodeTokenRequest
// object.
String CLIENT_SECRET_FILE = "/path/to/client_secret.json";
// Exchange auth code for access token
GoogleClientSecrets clientSecrets =
GoogleClientSecrets.load(
JacksonFactory.getDefaultInstance(), new FileReader(CLIENT_SECRET_FILE));
GoogleTokenResponse tokenResponse =
new GoogleAuthorizationCodeTokenRequest(
new NetHttpTransport(),
JacksonFactory.getDefaultInstance(),
"https://www.googleapis.com/oauth2/v4/token",
clientSecrets.getDetails().getClientId(),
clientSecrets.getDetails().getClientSecret(),
authCode,
REDIRECT_URI) // Specify the same redirect URI that you use with your web
// app. If you don't have a web version of your app, you can
// specify an empty string.
.execute();
String accessToken = tokenResponse.getAccessToken();
// Use access token to call API
GoogleCredential credential = new GoogleCredential().setAccessToken(accessToken);
Drive drive =
new Drive.Builder(new NetHttpTransport(), JacksonFactory.getDefaultInstance(), credential)
.setApplicationName("Auth Code Exchange Demo")
.build();
File file = drive.files().get("appfolder").execute();
// Get profile info from ID token
GoogleIdToken idToken = tokenResponse.parseIdToken();
GoogleIdToken.Payload payload = idToken.getPayload();
String userId = payload.getSubject(); // Use this value as a key to identify a user.
String email = payload.getEmail();
boolean emailVerified = Boolean.valueOf(payload.getEmailVerified());
String name = (String) payload.get("name");
String pictureUrl = (String) payload.get("picture");
String locale = (String) payload.get("locale");
String familyName = (String) payload.get("family_name");
String givenName = (String) payload.get("given_name");
For detailed info, find all the required steps and references at: https://developers.google.com/identity/sign-in/web/server-side-flow#step_1_create_a_client_id_and_client_secret

Error in using YouTube Reporting Services with Service Account

I'm getting below error when I try to accessing YouTube Reporting Services API using Service Account.
Google.GoogleApiException HResult=0x80131500
Message=Google.Apis.Requests.RequestError The caller does not have
permission [403] Errors [ Message[The caller does not have
permission] Location[ - ] Reason[forbidden] Domain[global] ]
When I searched online, somewhere I saw we need to have GSuite subscription in order to use service account with API, is it correct?
Here is my code snippet:
String serviceAccountEmail = "xxxxxxxxxxxxx#youtubereportingapi.iam.gserviceaccount.com";
var certificate = new X509Certificate2(#"xxxxxxxx.p12", "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { YouTubeReportingService.Scope.YtAnalyticsReadonly, YouTubeReportingService.Scope.YtAnalyticsMonetaryReadonly }
}.FromCertificate(certificate));
var youtubeReportingService = new YouTubeReportingService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = Assembly.GetExecutingAssembly().GetName().Name
});
var jobsListResponse = youtubeReportingService.Jobs.List().Execute(); // Here its throwing error
In order to use a service account you must share data with it. I have never heard of using GSuite with a YouTube account you can have the admin try and set up domain wide delegation to the service account. However to my knowledge there is no way to share YouTube data with a service account.
Service accounts are not supported by the YouTube APIs. You will need to use Oauth2 and save your refresh token for access later. The docs only mention OAuth2.

Azure AD userAssertion: Token missing scope "Directory.Read.All"

I have a Web API and a UI application that used ADAL library to call the Web API.
I already gave DELEGATED PERMISSIONS (Read directory data) for both Web API and UI application while registering the apps to Azure AD.
I have below code in Web API to save Token for log-In user,
private void ConfigureAuthentication(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters { SaveSigninToken = true, ValidAudience = ConfigurationManager.AppSettings["ida:Audience"] }
});
}
Now in Web API controllers, I am trying to get token to access Microsoft AD Graph API using below code,
var bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext as System.IdentityModel.Tokens.BootstrapContext;
string userName = "test#onmicrosoft.com";
string userAccessToken = bootstrapContext.Token;
UserAssertion userAssertion = new UserAssertion(bootstrapContext.Token, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);
var authContext = new AuthenticationContext(string.Format(CultureInfo.InvariantCulture, aadInstance, tenant));
var clientCred = new ClientCredential(clientId, appKey);
var result = await authContext.AcquireTokenAsync("https://graph.windows.net", clientCred, userAssertion);
accessToken = result.AccessToken;
The above code giving me token back but the scope values is below,
`scp: "User.Read"`
Question - Why the token not giving directory access (Directory.Read.All) as I already set Directory access?
`scp: "Directory.Read.All User.Read"`
Update:
I am missing Grant Permission for Read Directory Data under DELEGATED PERMISSIONS. After giving Grant Permission I am able to get token with scope scp: "Directory.Read.All User.Read"
If I understand correctly, you want to use Microsoft Graph API ,not Azure AD Graph API.
However, based on that the screenshot you post in this question is a v1 enpoint AAD Application, it does nothing about Microsoft Graph API which you're trying to approaching. So, whatever you changed on this application, the result shouldn be same. I suggest you register v2 enpoint Application in https://apps.dev.microsoft.com/
Here is a document which shows how to get auth tokens for using Microsoft Graph.
Hope this helps!

Azure OpenId Token validation

I am new to azure, tokens and so on...
I have "digged" microsoft documentation and google and stackoverflow, but still didn't get full understanding.
So I using openId with Owin library to connect to azure from web app(VS2013 .net 4.5.1). And I have next code to do it:
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(
CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
MetadataAddress = String.Format(aadInstance, tenant, policy),
AuthenticationType = policy,
              
ClientId = clientId,
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = AuthenticationFailed
,SecurityTokenValidated = OnSecurityTokenValidated
,AuthorizationCodeReceived = OnAuthorizationCodeReceived
,SecurityTokenReceived = OnSecurityTokenReceived
},
Scope = "openid profile",
ResponseType = "id_token"
};
);
}
private Task OnSecurityTokenValidated(SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
var identity = notification.AuthenticationTicket.Identity;
var claims = notification.OwinContext.Authentication.User.Claims;
ClaimsPrincipal.Current.AddIdentity(identity);
return Task.FromResult(0);
}
And it is working, but in microsoft documentation I found next instruction "Currently, ID tokens are signed but not encrypted. When your app receives an ID token, it must validate the signature to prove the token's authenticity and validate a few claims in the token to prove its validity. The claims validated by an app vary depending on scenario requirements, but your app must perform some common claim validations in every scenario."
But there is SecurityTokenValidated-callback , which have AuthenticationTicket. So do I still need to somehow validate token/ticked or now it is handled automatically (I been tough in army that nothing happening automatically, but still)?
The library that you are using handles the validation for you.
It will check the signature is what it should be based on the keys provided by Azure AD.
So you don't need to do manual checks, other than your app's specific checks. For example, an app might allow only members of a certain group to access the app. You would need to do that check if that is the case.

Resources