Azure AD authentication token fails web api authorization - c#

I have a web-api on Azure that requires authorization and I am using Azure AD to authenticate accounts and generate access tokens.
I can successfully acquire access tokens from Azure AD with ADAL for the same account in two different ways, but only one of them is authorized by the web-api, the other one fails.
The following is authenticating an account interactively and the token is authorized by the web-api
result = AuthenticationContext.AcquireTokenAsync(resource, clientId, redirectUri, new PlatformParameters(PromptBehavior)).Result;
where resource is web-api application id (guid).
The following is authenticating an account non-interactively with a given user name and password, but the token is not authorized by the web api
UserPasswordCredential cred = new UserPasswordCredential(userName, password);
result = AuthenticationContext.AcquireTokenAsync(resource, clientId, cred).Result;
where resource = https://{tenant}/{api name}.
The web-api call is as follows:
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = await httpClient.GetAsync(ApplicationCallUri);
Both ways return identical AuthenticationResult objects (apart from tokens and time stamps) and I cannot see why authorization fails for the second one.
The web-api response is "Authorization has been denied for this request."
Since authentication succeeds for both ways, I assume it must be something with at the web-api's side. Help is much appreciated. Thanks.

Thanks to juunas who pointed out the audience parameter I realized that the web api was set to expect tokens for only one the two audience values. I added a second option for bearer authentication and it works for both scenarios.
Thank you juunas!

Related

How to setup Active Directory authentication with Vue.js and a .NET Core API

I am re-writing one of our intranet sites as a .NET Core 3.1 WebAPI backend with a Vue.js frontend. I'm an experienced programmer, but new to some of these technologies. I need to setup authentication with Active Directory so local network users don't have to login to the site.
I've seen some things about using tokens and sending that in the API requests, but don't know where to start. Eventually, there will be a second Vue.js frontend for external (third-party) users who will need to authenticate with a username/password form. So the API needs to be able to handle both kinds of users.
How do I set this up? Any instructions or guidance is appreciated. I'm sure I'll have other questions as I get into it.
Thanks!
EDIT
I was able to get this method working to take a username/password and validate it against our Active Directory domain (requires the Microsoft.Windows.Compaibility package)
using System.DirectoryServices.AccountManagement;
private bool IsValidUserAndPasswordCombination(string username, string password)
{
bool isValid = false;
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "localdomainname.com"))
{
isValid = pc.ValidateCredentials(username, password);
}
return isValid;
}
Now I just need to know the best way to pass the current Windows user credentials to the API. The API is setup for anonymous authentication. Is there a way to get the credentials in Vue.js and pass them without using the authorization headers?
I think you can check this quesion that is analogue to yours.
So you can generate JWT Tokens manually (if you need it for authentication), checking users identity through LDAP Authentication.
EDIT
I think you cannot get the current user credentials inside a browser without a specific browser plugin... Maybe the only way is to prompt for user login like any other website and keep the Login Action Controller anonymous and use it to validate user.
When validated you can generate a JWT Token for the client to keep authenticated.
EDIT 2
To generate a JWT Token you can find may tutorials on the web, mainly you have to use the System.IdentityModel.Tokens.Jwt namespace eg.:
var tokenHandler = new JwtSecurityTokenHandler();
// The secret must be kept and used to validate the token
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim("id", user.Id.ToString()) }),
Expires = DateTime.UtcNow.AddDays(7), // maybe you want it to be shorter
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
// If you want you can add as many claims you need in your token (roles etc.)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
Send the tokenString to te client in the response to login request, and keep it in the client to send authenticated requests.
You also neet to configure the API to authorize users through JWT tokens and instruct it to accept only your one (see here for details)

Azure AD B2C get token programatically for unit testing

My scenario is simple I have a simple Azure Function with B2C authentication on it and I'm writing unit tests but I found an issue, I'm not able to authenticate to the azure functions programmatically.
I'm able to access through the browser and even I can grab the token and put it into the unit test and it works fine, but when I try to generate a token using the ClientID, TenantID, etc. I get a token, but 401 Unauthorized response on the Azure functions.
Is there a way to generate a valid B2C token programmatically (without login in the browser?
The approach I'm using so far:
public static async Task<AuthenticationResult> GetAccessToken(string resourceUri, string clientId, string clientSecret)
{
ClientCredential clientCredential = new ClientCredential(clientId, clientSecret);
string aadInstance = "https://login.microsoftonline.com/";
string tenant = "<mytenant>.onmicrosoft.com";
string authority = string.Concat(aadInstance, tenant);
AuthenticationContext authContext = new AuthenticationContext(authority);
return await authContext.AcquireTokenAsync(resourceUri, clientCredential);
}
I'm getting a token (EY.......) but is not valid, when I passed to the Azure Function request, it returns 401 Unauthorized.
Thanks in advance!
Ivan
A couple of months ago, Microsoft released a policy for resource owner password credentials flow, with that policy you can simulate a login passing the login details in a query as follows:
Create a ROPC policy in B2C
Register an application
Test the policy as follows:
https://te.cpim.windows.net/{B2C TENANT}/{ROPC B2C POLICY}/oauth2/v2.0/token?username={USERNAME}&password={password}&grant_type=password&scope=openid+{CLIENT ID}+offline_access&client_id=[CLIENT ID]&response_type=token+id_token
You can find more detailed info here
Your unit test is acquiring a token from the Azure AD v1.0 endpoint rather than the Azure AD B2C v2.0 endpoint.
Your Azure function is expecting the token to be issued by the Azure AD B2C v2.0 endpoint.
In the short term, you can consider acquiring the token from the Azure AD B2C v2.0 endpoint by replaying the browser requests using the HttpClient class.
In the near term, support for the resource owner password credential grant by Azure AD B2C will enable your unit test to acquire a token from the Azure AD B2C v2.0 endpoint by POSTing a user credential to the endpoint.

Sharepoint REST api and MVC AAD connect

My need is to execute this query https://<tenant>.sharepoint.com/_api/search/query?querytext=%27contenttype:articles%27 thru Sharepoint REST api from server side in C#.
I have Oauth2 connection from the MVC portal, so my goal is to retrieve token from connection and send it as bearer token to sharepoint endpoint.
I mean something like that
string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Startup.Authority, new NaiveSessionCache(userObjectID));
ClientCredential credential = new ClientCredential(clientId, appKey);
AuthenticationResult result = await authContext.AcquireTokenSilentAsync("https://<tenant>.sharepoint.com/", credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "https://<tenant>.sharepoint.com/_api/search/query?querytext=%27contenttype:articles%27");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = await client.SendAsync(request);
but obviously, I can not retrieve the token...
On another side, I have built an app with ADv2 and GraphServiceClient that is working well, but I don't know how to translate the query in graph model (and I don't have any admin-consent).
So, I have 2 ways to resolve my issue, I'll like better use the 2nd option with microsoft graph api, but any help is welcome.
Thank you.
Around Search
The graph search API has limited capabilities, first it will only search in the current site collection (drive) you're targeting, second I'm not sure at the moment it would support a search by content type (maybe with a $filter...)
But it could be an (easier) option if that fits your constraints.
Around auth & auth
In both cases (graph or SharePoint search), what happens when people get to your application (asp.net MVC) is that the authentication middleware takes care of redirecting the user to AAD, get an access token to your app, redirects it to your app which uses that access token to create a session on the app.
My point being: at this point, all you have are:
An access token to your app (not the graph, not SharePoint
A session against your app
You need to do a couple of things to get to SharePoint/the graph:
Intercept and keep the token server side (add it to the session?) if that's not already being done by your implementation of the middlewares
Use that access token + you app id/secret/certificate to get an access token to SharePoint/the graph against AAD
Make sure your application has permissions in AAD to talk to SharePoint/The proper graph API's
Here is a sample on how to get from "I have the access token to my app/api" to "I have an access token to the graph/SharePoint" using MSAL.
Note: I'm using a certificate here, but you could be using a secret instead
var cac = new ClientAssertionCertificate(ApplicationId, CertificateProvider.AppCertificate);
var ua = new UserAssertion(apiAccessToken);
authenticationResult = await authContext.AcquireTokenAsync(resource, cac, ua);
I'm not providing the code on how to intercept the token/get it here because your question is unclear on your current authentication and authorization configuration as well as what MVC "flavor" are you using (asp.net core + middlewares, classic + owin, something else?). I suggest you start another question with more details on that specific point.

Get user claims after federation authN and include it in requesting a JWT

I have an Owin based Web App and a backend Web API, they are authenticated against AAD and the workflow can be describe as below listed.
Web App authenticates end users against AAD using Federation Authentication.
Web App requests a JWT from AAD for accessing the backend Web API.
The main code for authenticating end users.
public void ConfigureAuth(IAppBuilder app)
{
// other code...
app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions
{
Wtrealm = realm,
MetadataAddress = adfsMetadata
});
}
The main code for getting JWT for accessing the backend API:
internal async Task<string> GetAccessToken()
{
var authContext = new AuthenticationContext(authority);
var credential = new ClientCredential(clientId, appKey);
var result = await authContext.AcquireTokenAsync(apiId, credential);
// Here, what I wanted is to use the other overloaded method
// authContext.AcquireTokenAsync(apiId, credential, userAssertion);
// But to instantiate a UserAssertion instance, the only way is
// to use the constructor new UserAssertion(assertionString)
// and the assertionString should be in JWT format
// unfortunately, the assertionString from Ws-Federation auth is
// for sure in SAML2 format. So, the question is:
// Give I am using Ws-Federation auth protocal, How can I pass the
// user information in requesting a JWT to backend API resource?
return result.AccessToken;
}
Generally, the whole authentication workflow is OK, I can both authenticate end users and get JWT for accessing backedn APIs. But the problem is that there is no end user claims in the JWT. I am sure I should get users claims from the federation authentication result and then put them in the process of requesting the JWT. Unfortunately, with all methods, libraries and classes I didn't find a solution to do that.
BTW, https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect gives an example how to obtain a JWT with end user claims included, but the solution does not work with my scenario as I am using Federation authentication rather than OpenID Connect.
Edit
To make the question clear: in the web app, I would like to request a JWT token for accessing the backend web api by using the method AuthenticationContext.AcquireTokenAsync.
From my demo code, you can see I am using the AcquireTokenAsync(apiId, clientCredential) overloaded verion. But this version does not attach the end users claims inside. Actually what I needed is the AcquireTokenAsync(apiId, clientCredential, userAssertion) overloaded method.
However, to instantiate a UserAssertion, I need the user assertion string which is the AccessToken from user authentication result. Unfortunetaly, the UserAssertion class only accept JWT format assertion string, but the Ws-Federation authentication returns the SAML2 format assertion string, so I am not able to instantiate a UserAssertion instance.
So, my question is: given the condition that I am using Ws-Federation authentication protocol for authenticating an end user, in the backend how can I pass the user assertion information (it is in SAML2 format) to AAD for requesting a JWT for a backend api resource?
AAD provides "canned" claims. There are no claims rules to add other attributes to the token.
Refer: Supported Token and Claim Types.
If you want other attributes, you need to use the Graph API.

Calling Azure AD secured web API with client / secret

I've been at this whole day and it's driving me NUTS. I've got a web API published on Azure which is secured with Azure AD authentication. I've been using this API in combination with a mobile app for quite a while now. On the mobile app, I use the library for client authentication (Microsoft account which is in AD) and this works perfectly. Within the app, API requests are authenticated with the X-ZUMO-AUTH header.
Now for a new part of the application, I've got a C# console app which also needs to be able to call the app. This needs to run stand-alone, so without asking for user credentials. So I created an application in Azure AD, gave it permissions to the Web API instance and got me some code to get an authentication token. That token is then passed into the HttpClient object:
AuthenticationContext ac = new AuthenticationContext(authority);
ClientCredential clientCred = new ClientCredential(clientID, clientSecret);
AuthenticationResult authenticationResult = await ac.AcquireTokenAsync(resource, clientCred);
string authToken = authenticationResult.AccessToken;
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, endpoint);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
HttpResponseMessage response = await client.SendAsync(request);
Server auth is set-up like this:
string aadTenant = configProvider.GetConfigurationSettingValue("ida.Tenant");
string aadAudience = configProvider.GetConfigurationSettingValue("ida.Audience");
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = aadTenant,
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = aadAudience
},
});
I have triple checked all the variables. I also verified that the Bearer token is correctly passed to the service (Fiddler shows it). Everything seems absolutely fine, but still, the server keeps responding with Unauthorized.
My hunch is that this has something to do with the Web API. It's been set up as mobile services back-end. I'm suspecting that this is somehow interfering with 'normal' authentication schemes and the 401 is coming from mobile services not understanding this authentication request, even though the bearer authentication has been setup. But this is just a hunch and I'm not sure whether it actually makes sense or not.
I've seen the related questions on this topic, and all of the samples (used this one as the primary source of inspiration). I've tried all of the variations out there, to no avail. So what I'm looking for is ways to find out where the actual problem is. "Unauthorized" is a bit vague to say the least, so I'm trying to find out why the calls are returned with this response. Any help is greatly appreciated.
Additional endpoint info
As requested, here's some info on the endpoints I'm using:
authority = https://login.microsoftonline.com/{aad-tenant-name}
Also tried the OAuth2 token endpoints listed on the management page:
authority = https://login.microsoftonline.com/{aad-guid}/oauth2/token
authority = https://login.microsoftonline.com/{aad-guid}/oauth2/authorize
For the client, information I'm passing in the ClientID and generated secret I got from the AAD Application page for the application I created. I have granted the application permissions to access the App Service instance for my web API endpoint. Those calls are going to:
https://{app service url}.azurewebsites.net/api/controller
Whilst trying to get this going I noticed more weird behavior. I thought for now I'd just set this particular controller to [AllowAnonymous] so that I could call it without any authentication. But when I do so and omit passing in the Bearer token, I still get Unauthorized as response. That kind of enforces my idea that there's something wrong on the server side and that this might have something to do with how the mobile app stuff is strapped to the webapi controllers.
Never mind, the controller being forced to authorize was caused by Web App level authentication settings, documented here

Categories

Resources