Authenticating sharepoint rest api with SSL cert - sharepoint

I am trying to access the sharepoint rest apis from Azure api management service. I need to send an access token for the request, But I am not sure how we can get the access token.
I am getting the access token in a console application using the following code. I used Microsoft.Identity.Client library in it. Anyone have any idea, how we can translate this code to APIM.
using Microsoft.Identity.Client;
using System;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
public class TokenProvider
{
public static async Task<string> GetAccessTokenAsync(string endpoint)
{
var clientId = "<<AAD_APP_CLIENT_ID>>";
var tenantId = "<<AAD_TENANT_ID>>";
using var certificate = GetCertificate(
Path.Combine(Environment.CurrentDirectory, "MyAppCertificate.pfx"),
"<<CERTIFICATE_PASSWORD>>");
var confidentialClient = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.WithCertificate(certificate)
.Build();
var token = await confidentialClient
.AcquireTokenForClient(new[] { $"{endpoint.TrimEnd('/')}/.default" })
.ExecuteAsync();
return token.AccessToken;
}
private static X509Certificate2 GetCertificate(string path, string password)
{
return new X509Certificate2(path, password, X509KeyStorageFlags.MachineKeySet);
}
}

Please check if the below steps that helps to workaround:
APIM has a Send Request policy that you can use in your inbound policy, along with the C# expression, to initiate any request before calling your backend services.
Here is an article gives information about how to implement Access Token Acquisition, Caching, and Renewal within your policy. To create the SharePoint online token, refer to this MS Q&A on what URL endpoint call you need to initiate and update your policy accordingly.
Refer this MSFT Documentation for more information on SharePoint REST services.
In the send-request policy, use the client certificate to authenticate. Authenticate policy is used to authenticate with a backend service using the client certificate, but authentication-certificate policy can be used at the end of your send-request. The certificate, which is identified by its thumbprint, must first be installed in API Management. Refer here for more information.

Related

What is the best practice to authenticate an Azure AD registered app?

I am developing an application which is based on GCP (Specifically it runs on the Google Cloud Composer (a managed version of Airflow)). From there I would like to connect to the Microsoft Graph API of another organization. So I want the application to be running in the background and every x minutes retrieve new emails from the inbox of an external organization, then do some processing and based on that perform some actions in said mailbox through the Graph API.
I am trying to figure out the best practice on how to secure this connection. I believe I could use the client secret of the registered application and then store that in an azure keyvault. Now I am not sure how I should authenticate my registered app such that it can retrieve this client secret which in turn can be used to access the Graph API?
As you can probably tell I'm not quite sure whether this makes sense or what a better approach would be? Almost all the information I am finding is concerning managed identities, however if I understood correctly these can only be used when the application is running natively on Azure, which for me is not the case.
Would really appreciate any help!
If I don't misunderstand, you can refer to my sample code, pls note you need to add key vault access policy first. And you may refer to this doc to know about the default azure credential.
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace test0430callingapi.Controllers
{
public class HelloController : Controller
{
public async Task<string> IndexAsync()
{
const string secretName = "clientsecret";
var kvUri = "https://key_vault_name.vault.azure.net/";
var a = new DefaultAzureCredential();
var client = new SecretClient(new Uri(kvUri), a);
var secret = await client.GetSecretAsync(secretName);
string secretVaule = secret.Value.Value;
//azure ad client credential flow to generate access token
IConfidentialClientApplication app;
app = ConfidentialClientApplicationBuilder.Create("azure_ad_app_clientid")
.WithClientSecret(secretVaule)
.WithAuthority(new Uri("https://login.microsoftonline.com/your_tanent_name.onmicrosoft.com"))
.Build();
AuthenticationResult result = null;
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
result = await app.AcquireTokenForClient(scopes)
.ExecuteAsync();
string accesstoken = result.AccessToken;
// this token can be used to call https://graph.microsoft.com/v1.0/users/user_id/mailFolders/{id}/messages
return accesstoken;
}
}
}
I used client credential flow here which don't need to make users sign in, and this api support application permission. If you use an api which only supports delegated permission, it can't work. Here's the api calling response.

Get PowerBI access token using UserAssertion

I have an Azure Function App with Easy Auth enabled. I want to get an access token to access to power bi with the permissions of the logged in user, so that user can directly access power bi from frontend.
I have registered an application on Azure AD with permissions to power bi and I created a HTTP triggered endpoint to return the access token to frontend.
I don't have any username or password in the function app, only the ID token of the user. Is there any way I can require an access token to access power bi on behalf of the user using the ID token or UserAssertion?
My implementation is here:
var context = new AuthenticationContext("https://login.windows.net/common/oauth2/authorize");
var res = await context.AcquireTokenAsync("https://analysis.windows.net/powerbi/api",
clientId, new Microsoft.IdentityModel.Clients.ActiveDirectory.UserAssertion(idToken)).ConfigureAwait(false);
return new OkObjectResult(new { Token = res.AccessToken });
But when I try to run this code, it throws me this exception
System.Private.CoreLib: Exception while executing function: GetPowerBiToken. Microsoft.IdentityModel.Clients.ActiveDirectory: AADSTS50027: JWT token is invalid or malformed.
Is this the correct way to get the token?
Per my understanding, you have a public client to log in users. This client will call APIs on the Azure function app and your function app will call PowerBi to do some tasks on behalf of users who logged in on your public client.
In this whole process, you should register two Azure AD apps :
1.An App for your public client, you should grant it delegated permission to access your Azure function App so that users could login and get an access token to call your function app .
2.An app for your Azure function app,you should grant it delegated permission related to powerbi, so that it could call powerbi apis on behalf of users.
So pls follow the steps below to finish this process :
1.On your client side , login users and get an access token which requested resources is your function app.
2.Call your function app with that access token so that your function app could use on-behalf-of flow to get an access token to call powerbi resources.
I have tested this process in an simple console app and it works perfectly for me, and the code is what you are looking for I think :
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AzureADUserAssertion
{
class Program
{
static void Main(string[] args)
{
var functionClientId = "<function azure ad app id>";
var functionSecret = "<function azure ad app secret>";
var functionCred = new ClientCredential(functionClientId, functionSecret);
var access_token_from_client_side = "<access token value>";
var context = new AuthenticationContext("https://login.windows.net/common/");
var res = context.AcquireTokenAsync("https://analysis.windows.net/powerbi/api",
functionCred, new Microsoft.IdentityModel.Clients.ActiveDirectory.UserAssertion(access_token_from_client_side, "urn:ietf:params:oauth:grant-type:jwt-bearer")).ConfigureAwait(false).GetAwaiter().GetResult();
Console.WriteLine( res.AccessToken );
Console.ReadKey();
}
}
}
Result :
Check this token :
Hope it helps .

Service to service authentication in Azure without ADAL

I configured azure application proxy for our on-premise hosted web service and turned on Azure AD authentication. I am able to authenticate using ADAL but must find a way to get the token and call web service without ADAL now (we are going to use this from Dynamics 365 online and in sandbox mode I can't use ADAL). I followed some examples regarding service to service scenario and I successfully retrieve the token using client credentials grant flow. But when I try to call the app proxy with Authorization header and access token, I receive an error "This corporate app can't be accessed right now. Please try again later". Status code is 500 Internal server error.
Please note the following:
I don't see any error in app proxy connectors event log.
I added tracing on our on-premise server and it seems like the call never comes there.
If I generate token with ADAL for a NATIVE app (can't have client_secret so I can't use client credentials grant flow), I can call the service.
I created an appRole in manifest for service being called and added application permission to the client app.
This is the way I get the token:
public async static System.Threading.Tasks.Task<AzureAccessToken> CreateOAuthAuthorizationToken(string clientId, string clientSecret, string resourceId, string tenantId)
{
AzureAccessToken token = null;
string oauthUrl = string.Format("https://login.microsoftonline.com/{0}/oauth2/token", tenantId);
string reqBody = string.Format("grant_type=client_credentials&client_id={0}&client_secret={1}&resource={2}", Uri.EscapeDataString(clientId), Uri.EscapeDataString(clientSecret), Uri.EscapeDataString(resourceId));
HttpClient client = new HttpClient();
HttpContent content = new StringContent(reqBody);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/x-www-form-urlencoded");
using (HttpResponseMessage response = await client.PostAsync(oauthUrl, content))
{
if (response.IsSuccessStatusCode)
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(AzureAccessToken));
Stream json = await response.Content.ReadAsStreamAsync();
token = (AzureAccessToken)serializer.ReadObject(json);
}
}
return token;
}
AzureAccessToken is my simple class marked for serialization.
I assume it must be something I haven't configured properly. Am I missing some permissions that are required for this scenario?
Any help is appriciated.

Azure appservice with Client certificate based or AAD token based authentication

Need client certificate based or AAD token based authentication enabled web api hosted in azure app service.
I am migrating one web API from classic cloud service to azure app service.
The API supports calls with valid certificates or valid AAD token.
Code is given below:
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
ClaimsPrincipal principal;
var cert = request.GetClientCertificate();
if (cert != null)
{
//authenticate client certificate
//Set principal from client certificate
}
else
{
//get AAD token
//authenticate & set principal
}
return await base.SendAsync(request, cancellationToken);
}
The issue is in App service for certificate based calls request.GetClientCertificate() is returning null instead of X509Certificate2 object. So not able to authenticate certificate based calls.
I have tried below link as well but in that case calling without certificate is not possible as its making required SSL certificate on for whole website.
https://learn.microsoft.com/en-us/azure/app-service-web/app-service-web-configure-tls-mutual-auth
There are lot of details missing in your explanation. The shared code snippet is useless.
In Azure App Service, there is a which sits in front of the VM where the application is hosted. When you enable TLS Mutual Auth for your web app, it is enabled for the entire app. Currently there is no option to do it for specific pages or sub-folders.
When the clients accesses the site, the Front-End prompts them for the client certificate. Assuming the client provides the certificate to the Front-End, it then passes this certificate to the back end VM in the form of a host header "X-ARR-ClientCert".
I dont see this being used anywhere in the above code snippet. This is also explained in the article (Azure App Service TLS Mutual Auth) which you have linked in your question:
protected void Page_Load(object sender, EventArgs e)
{
NameValueCollection headers = base.Request.Headers;
certHeader = headers["X-ARR-ClientCert"];
if (!String.IsNullOrEmpty(certHeader))
{
try
{
byte[] clientCertBytes = Convert.FromBase64String(certHeader);
certificate = new X509Certificate2(clientCertBytes);
You need to read the contents X-ARR-ClientCert header and then convert it to a X509Certificate2 object and then run your checks against this.

Using Oauth to protect WebAPI with Azure active directory

I have browsed all the tutorials regarding using Oauth to protect WebAPI in Azure active directory online. But unfortunately, none of them can work.
I am using VS 2017 and my project is .net core.
So far what I have tried is:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
ervices.AddAuthentication(); // -----------> newly added
}
In "Configure", I added:
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Authority = String.Format(Configuration["AzureAd:AadInstance"], Configuration["AzureAD:Tenant"]),
Audience = Configuration["AzureAd:Audience"],
});
Here is my config:
"AzureAd": {
"AadInstance": "https://login.microsoftonline.com/{0}",
"Tenant": "tenantname.onmicrosoft.com",
"Audience": "https://tenantname.onmicrosoft.com/webapiservice"
}
I have registered this "webapiservice" (link is: http://webapiservice.azurewebsites.net) on my AAD.
Also, to access this web api service, I created a webapi client "webapiclient" which is also a web api and also registered it on my AAD and requested permission to access "webapiservice". The webapi client link is: http://webapiclient.azurewebsites.net
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://webapiservice.azurewebsites.net/");
//is this uri correct? should it be the link of webapi service or the one of webapi client?
HttpResponseMessage response = client.GetAsync("api/values").Result;
if (response.IsSuccessStatusCode)
{
var result = response.Content.ReadAsAsync<IEnumerable<string>>().Result;
return result;
}
else
{
return new string[] { "Something wrong" };
}
So theoretically, I should receive the correct results from webapiservice. but I always received "Something wrong".
Am I missing anything here?
You need an access token from Azure AD.
There are plenty of good example apps on GitHub, here is one for a Daemon App: https://github.com/Azure-Samples/active-directory-dotnet-daemon/blob/master/TodoListDaemon/Program.cs#L96
AuthenticationResult authResult = await authContext.AcquireTokenAsync(todoListResourceId, clientCredential);
This app fetches an access token with its client id and client secret for an API. You can follow a similar approach in your case. You can just replace todoListResourceId with "https://graph.windows.net/" for Azure AD Graph API, or "https://graph.microsoft.com/" for Microsoft Graph API, for example. That is the identifier for the API that you want a token for.
This is the way it works in AAD. You want access to an API, you ask for that access from AAD. In a successful response you will get back an access token, that you must attach to the HTTP call as a header:
Authorization: Bearer accesstokengoeshere......
Now if you are building a web application, you may instead want to do it a bit differently, as you are now accessing the API as the client app, not the user. If you want to make a delegated call, then you will need to use e.g. the Authorization Code flow, where you show the user a browser, redirect them to the right address, and they get sent back to your app for login.
To call web api protected by azure ad , you should pass this obtained access token in the authorization header using a bearer scheme :
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);

Resources