Authenticate to Azure API App using ADAL - c#

I have an Azure API App marked as "Public (authenticated)" and set up an Azure Active Directory identity in the associated gateway as detailed in Protect an API App.
I then created a native application in the same Azure Active Directory Tenant and added permission to access the Gateway in the delegated permissions.
Using ADAL and the following code, I'm able to successfully authenticate and get an access token, but I can't figure out how to use it to access my API app.
string Tenant = "[xxx].onmicrosoft.com";
string Authority = "https://login.microsoftonline.com/" + Tenant;
string GatewayLoginUrl = "https://[gateway].azurewebsites.net/login/aad";
string ClientId = "[native client id]";
Uri RedirectUri = new Uri("[native client redirect url]");
async Task<string> GetTokenAsync()
{
AuthenticationContext context = new AuthenticationContext(Authority);
PlatformParameters platformParams = new PlatformParameters(PromptBehavior.Auto, null);
AuthenticationResult result = await context.AcquireTokenAsync(GatewayLoginUrl, ClientId, RedirectUri, platformParams);
return result.AccessToken;
}
I've tested the API app manually entering an x-zumo-auth header I get in Chrome and it works then, but not with a token I get using ADAL. I've also tried the browser forms described in their sample code which works but doesn't give me a refresh token.
How do I need to set up my authentication code so I can use a TokenCache and ADAL with my API app?

Generally you pass the access token in the Authorization header when when calling a web api:
Authorization: Bearer ThisIsTheAccessTokenYouRecievedFromADAL

You may want to use AppServiceClient to authenticate the user and invoke a protected API App endpoint. Install Microsoft.Azure.AppService SDK (-pre) Nuget package to your client project.
You can find more details in the AzureCards samples on GitHub - https://github.com/Azure-Samples/API-Apps-DotNet-AzureCards-Sample

Related

Does Google.Apis.Auth.OAuth2 C# library support PKCE for Desktop applications?

I am working on integrating a C#/.Net Framework desktop application to Google Sheets API. I have registered my application at developers.google.com where I chose type Desktop and I received a clientId and a clientSecret.
On the client side I want to use the OAuth2 Authorization Code flow to obtain an access token on behalf of the user. I am using Google.Apis.Sheets.v4 NuGet package which exposes some abstractions for obtaining the authorization code and access token(eg: GoogleWebAuthorizationBroker.AuthorizeAsync) and for using Sheets API(eg: SheetsService) . But it requires both the Client Id and Client Secret in order to obtain the token.
Since this is a Desktop application how safe is to expose the Client Secret on the client side where all the users who installed my app are able to decompile the app and get the credentials? Is there any PKCE approach like for Web SPA where you don't need a client secret to obtain a token?
This a working version of the flow:
private static async Task<UserCredential> GetCredential()
{
var scopes = new[] { "https://www.googleapis.com/auth/spreadsheets" };
var codeReceiver = new LocalServerCodeReceiver();
Console.WriteLine(codeReceiver.RedirectUri);
var authorizeAsync = await Google.Apis.Auth.OAuth2.GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets
{
ClientId = "some-client-id",
ClientSecret = "some-client-secret"
},
scopes,
"user",
CancellationToken.None,
new FileDataStore("Google.Apis.Auth.OAuth2.Responses.TokenResponse-user2"),
codeReceiver: codeReceiver
);
return authorizeAsync;
}
var sheetsService = new Google.Apis.Sheets.v4.SheetsService(new BaseClientService.Initializer
{
HttpClientInitializer = await GetCredential(),
ApplicationName = "my-app-name",
});
var request = sheetsService.Spreadsheets.Values.Get(spreadsheetId, range);
But as you can see it requires ClientSecret to be provided. Without it, I get an error from Google API Authorization endpoint saying the ClientSecret is missing.
My expectation is for ClientSecret not to be required to obtain an access token for Desktop/Installed apps.
I'm not exactly sure why you think users of your desktop application will see your client credentials.
When you compile your application ensure that you're client id and secret are complied into it. Don't have it in a clear text settings file.
While it is true that someone could decompile your application and get your client credentials this is a accepted risk.
The only other option would be to configure an endpoint on your own domain which all requests for your desktop applikation would need to go through on startup to get the credentials. While this would work it leads to other issues being that if your delivery service goes down your application would not work also if the user doesn't have internet it wouldn't work either.
There is really no perfect solution for desktop apps and Google apis unfortunately
To be clear you need a client id and client secret for authorization token flow with desktop applications, there is no authorization flow that didn't require a secret

How to assign Service Principal to workspace programmatically in C#

I have created a service principal in AAD and am able to assign to the workspace manually in https://app.powerbi.com/home
I want to assign the service principal to all the workspaces programmatically.
Is there any way to do it?
Please help
Thanks
Yes, you can use the Power BI REST API and call Update Group User to add the service principal to the workspace:
Request:
PUT https://api.powerbi.com/v1.0/myorg/groups/f089354e-8366-4e18-aea3-4cb4a3a50b48/users
Request body:
{
"identifier": "1f69e798-5852-4fdd-ab01-33bb14b6e934",
"groupUserAccessRight": "Admin",
"principalType": "App"
}
To use the API, you must authenticate yourself, for example with ADAL or MSAL. Here is an example how to get an access token with MSAL:
private static async Task<string> GetToken()
{
// TODO: Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory -Version 2.21.301221612
// and add using Microsoft.IdentityModel.Clients.ActiveDirectory
//The client id that Azure AD created when you registered your client app.
string clientID = "{Client_ID}";
//RedirectUri you used when you register your app.
//For a client app, a redirect uri gives Azure AD more details on the application that it will authenticate.
// You can use this redirect uri for your client app
string redirectUri = "https://login.live.com/oauth20_desktop.srf";
//Resource Uri for Power BI API
string resourceUri = "https://analysis.windows.net/powerbi/api";
//OAuth2 authority Uri
string authorityUri = "https://login.microsoftonline.com/common/";
//Get access token:
// To call a Power BI REST operation, create an instance of AuthenticationContext and call AcquireToken
// AuthenticationContext is part of the Active Directory Authentication Library NuGet package
// To install the Active Directory Authentication Library NuGet package in Visual Studio,
// run "Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory" from the nuget Package Manager Console.
// AcquireToken will acquire an Azure access token
// Call AcquireToken to get an Azure token from Azure Active Directory token issuance endpoint
AuthenticationContext authContext = new AuthenticationContext(authorityUri);
var token = authContext.AcquireTokenAsync(resourceUri, clientID, new Uri(redirectUri)).Result.AccessToken;
Console.WriteLine(token);
Console.ReadLine();
return token;
}
This token must be added to the request headers, when you call the API:
//Add token to the request header
request.Headers.Add("Authorization", String.Format("Bearer {0}", token));

How to get access token to call MS Graph on behalf of a user in the console app using MSAL?

I have a SPA application that communicates with my backend Web API using AAD v2 authentication. Now I'm developing a console app to call Microsoft Graph on behalf of the user signed into the SPA app.
I have a valid access token of the user (used to call backend Web API). I want to use this access token to request a new token for accessing MS Graph.
Here is the code of the console app for requesting a new access token with MS Graph scopes using MSAL.NET:
string clientId = "<clientId>";
string clientSecret = "<clientSecret>";
string accessToken = "<validAccessTokenForWebApi>";
string assertionType = "urn:ietf:params:oauth:grant-type:jwt-bearer";
string[] scopes = new string[] { "User.Read", "Mail.Send" };
string graphAccessToken = null;
try
{
var app = ConfidentialClientApplicationBuilder
.Create(clientId).WithClientSecret(clientSecret).Build();
var userAssertion = new UserAssertion(accessToken, assertionType);
var result = app.AcquireTokenOnBehalfOf(scopes, userAssertion)
.ExecuteAsync().GetAwaiter().GetResult();
graphAccessToken = result.AccessToken;
}
catch (MsalServiceException ex)
{
throw;
}
But when I call app.AcquireTokenOnBehalfOf() I get an exception:
AADSTS50013: Assertion failed signature validation. [Reason - The provided signature value did not match the expected signature value., Thumbprint of key used by client: 'BB839F3453C7C04068B078EDADAB8E6D5F382E76', Found key 'Start=06/04/2019 00:00:00, End=06/04/2021 00:00:00']
What is the reason? What is the right way of getting access token on behalf of a user?
UPDATE - why do I need console app?
I could call Graph API directly from my backend API, but some actions may be delayed by the user (e.g. send mail using Graph API after 30 minutes). That is why I need to do this using the console app that runs on schedule.
If you want to use OAuth 2.0 On-Behalf-Of flow, I think you do not need to develop a console application to call graph api. You can directly use your backend Web API application to acquire access token then call Microsoft Graph. According to my understanding, you just do these steps
Sign-in the user in the client application
Acquire a token to the Web API (TodoListService) and call
it.
The Web API then calls another downstream Web API (The Microsoft
Graph).
For more details, please refer to the sample.
Regarding how to get access token with on behalf flow in the console application, The detailed steps are as below.
Register the web api app
Register APP
Create Client secrets
Configure permissions to access Graph API
Configure an application to expose web APIs(Add scope for the api)
Register the SAP app
Register APP
Create Client secrets
Configure permissions to access web API
Configure known client applications for web API application
In the Azure portal, navigate to your Web api
app registration and click on the Manifest section.
Find the property knownClientApplications and add the Client IDs of the SAP applications
Get access token to call web api
GET https://login.microsoftonline.com/common/oauth2/v2.0/authorize
?scope=<you web api scope> openid
&redirect_uri=<your sap app redirect url>
&nonce=test123
&client_id=<you sap app client id>
&response_type=id_token token
get access token with on behalf flow
REST API
POST https://login.microsoftonline.com/common/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
&client_id=<you web api client id>
&assertion=<you acess token you get in above steps>
&client_secret=<you app secret>
&scope=https://graph.microsoft.com/user.read
&requested_token_use=on_behalf_of
MSAL.net Code
string[] scopes = { "user.read" };
string accesstoken = "";
string appKey = "yor web api client secret";
string clientId = "your web api application id";
var app = ConfidentialClientApplicationBuilder.Create(clientId)
.WithClientSecret(appKey)
.Build();
UserAssertion userAssertion = new UserAssertion(accesstoken,
"urn:ietf:params:oauth:grant-type:jwt-bearer");
var result = app.AcquireTokenOnBehalfOf(scopes, userAssertion).ExecuteAsync().Result;
Console.WriteLine(result.AccessToken);

Web API on-behalf-of adal id_token error

I have created a Web API in Azure.
This Web API makes some calls in SharePoint Online. Some of the api calls are on-behalf-of.
This Web API works fine until 01.05.2018 - and it works fine on old app services, which were created before 01.05.2018.
A microsoft staff member said:
As part of our security hardening efforts we do not allow id_token
redemption for any application created after 2018-05-01 00:00:00.
During the log in process of adal, I got the id_token. The id_token has got the same value as the access_token:
When I call the web api, I will send this token as bearer token.
The Web API takes this token (string accessToken) and starts the method 'AcquireTokenAsync':
var clientID = ConfigurationManager.AppSettings["ClientID"];
var clientSecret = ConfigurationManager.AppSettings["ClientSecret"];
var tenant = ConfigurationManager.AppSettings["Tenant"];
var appCred = new ClientCredential(clientID, clientSecret);
var authContext = new AuthenticationContext(
"https://login.microsoftonline.com/" + tenant);
var resource = new Uri(sharePointUrl).GetLeftPart(UriPartial.Authority);
var authResult = await authContext.AcquireTokenAsync(resource, appCred,
new UserAssertion(accessToken));
return authResult.AccessToken;
But in the line which calls 'AcquireTokenAsync' I have got the error message:
AADSTS240002: Input id_token cannot be used as 'urn:ietf:params:oauth:grant-type:jwt-bearer' grant
But where is the problem?
The problem is that you use the same application identity in the front-end and back-end, and MS does not allow you to use the Id token (which you use as an access token here because of the former) to get another access token.
A possible solution:
Register another application (the front-end JS app should be a Native app)
It should acquire an access token for your back-end API using either the API's client id or app Id URI as the resource
Then the API can exchange the access token for another access token
If this is a multi-tenant app, the migration is probably not going to be easy.
If it's single-tenant, then all should be possible.
Your front-end app should of course require permission to call your back-end API in Azure AD, and that permission should be granted.
Another solution would be to acquire the other access token in the front-end using ADAL.JS instead of using on-behalf-of in the back-end and attaching that to all API requests in addition to the Id token.

How to validate user credentials in Azure AD with Web application / WebAPI

I have a web application. In the home page, user will enter the credentials, and system should validate against Azure AD and proceed further.
When I use a native app, and use UserCredentials, it validates the user, but if I use same approach for WebAPI, it throw the exception
The request body must contain the following parameter: 'client_secret
or client_assertion'
When I use the WebAPI using clientCredentials, it generates the accessToken, which do not validate the user credentials. I also tried passing the credentials as part of httpclient headers in the consequent calls, it is working despite the wrong credentials.
string AzureADSTSURL = "https://login.windows.net/{0}/oauth2/token?api-version=1.0";
string GraphPrincipalId = "https://graph.windows.net";
string userid = "userid";
string password = "pass";
string tenantId = "axxx"; // webapi
string clientId = "bxxx";
string clientSecret = "cxxx";
string authString = String.Format(AzureADSTSURL, tenantId);
var context = new AuthenticationContext(authString);
UserCredential userCredentials = new UserCredential(userid, password);
AuthenticationResult authenticationResult = context.AcquireToken(GraphPrincipalId.ToString(), clientId, userCredentials); // this works only if the clientId corresponds to a native app
ClientCredential clientCredential = new ClientCredential(clientId, clientSecret);
AuthenticationResult result = context.AcquireToken(GraphPrincipalId, clientCredential);
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(result.AccessToken, Convert.ToBase64String(UTF8Encoding.UTF8.GetBytes(userid + ':' + password)));
httpClient.GetAsync("http://localhost:11455/Login.aspx");
Is there a way to validate the credentials without using native app? Graph API is not a right choice for this I believe.
I was trying to do the same thing, and ran into the same error:
The request body must contain the following parameter: 'client_secret or client_assertion'
I banged my head on it for a while, and then hit up AzureSupport on twitter.
Turns out this type of auth is only supported if you set up the Azure AD App as Native Client Application. If you set it up as a Web Application then you get that error because the only way to access a web application in Azure AD is via client ID + secret.
You can have multiple apps on top of a single AD, so you can just set up a second app as native client to authenticate the same users in that directory.
You can certainly use WebAPI. Here's how to set it up:
If you use Azure Web Apps, which supports ASP.NET MVC then you can use the Azure Active Directory authentication mechanism. Here is a blog post describing how to set it up: https://azure.microsoft.com/en-us/documentation/articles/app-service-mobile-how-to-configure-active-directory-authentication/
Once you have that, auth will be enabled for your app and you can configure the AAD app in the portal. See this blog post for more details: http://blogs.technet.com/b/ad/archive/2014/12/18/azure-active-directory-now-with-group-claims-and-application-roles.aspx
Here is an example which shows how to read AAD group claims from a web app: https://github.com/Azure-Samples/active-directory-dotnet-webapp-groupclaims
Once you have the tokens, you can then call a Web API, which is shown by this example: https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect
There's a good list of AAD examples here: https://azure.microsoft.com/en-us/documentation/articles/active-directory-authentication-scenarios/
Short answer: No
I would consider this article to be the authoritive answer as to why.
No web sites/confidential clients
This is not an ADAL limitation, but an AAD setting. You can only use those flows from a native client. A confidential client, such as a web site, cannot use direct user credentials.
Direct use of username an password is [...] a bit of a Faustian pact – the price you pay for its directness is in the many limitations it entails and the reduced flexibility that it imposes on any solution relying on it.

Categories