Securing .NET Framework Web API with Azure AD (Client credentials flow) - asp.net-web-api

I have a .NET 4.7 Web API project (not .NET CORE).
I am trying to setup authentication with an Azure AD directory, I setup an application in my AD, and I got the client id (application id)
I would like to use the Client Credentials grant type. So I went ahead and retrieved a token via the access token URL https://login.microsoftonline.com/HIDDEN/oauth2/v2.0/token I am passing in the client id, and secret, for this I am using Postman
Now in my project I've implemented the following logic in my web api project:
var clientId = "AZURE APPLICATION ID";
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
{
AllowedAudiences = new List<string> { clientId },
TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidAudience = clientId
}
});
On my controller, I applied the [Authorize] attribute
When calling the API controller endpoint (making sure I am passing in the Authorization header with the value "Bearer MYTOKEN") I get the error returned in Postman:
"Message": "Authorization has been denied for this request."
Is there a way I can dive deeper to figure out what might be wrong?
I don't see anything in my output window in visual studio, are there some events I can hook into why it is failing?
EDIT: Adding more information per Carl:
The token seems to be valid, here are the results from jwt.ms, i even setup an "admin" role via the manifest:
Here is my code, I am not specifying the public signature (not sure how to do that yet), but I've even turned off IssueSignature validation.
This is what my controller looks like:
My fiddler request and response (does having an http endpoint instead of https for local development make a difference?) I don't believe it does:

Inspect your access token and ensure the aud claim value equals the clientId. Usually the aud claim will be something like api://clientId which is not what you have setup in your code. If that's the case set it as "api://" + clientId

You should get a 401 error, which means that the aud of your token is not your api. The cause of the error is usually that you set the wrong scope when requesting the token. I used the client credential flow Make a demo for you:
You need to create two applications in Azure ad, one representing the client application and the other representing the api application, and then use the client application to call the Web api application.
First, you need to expose the api of the application representing the web api, you can configure it according to the following process:
Azure portal>App registrations>Expose an API>Add a scope>Add a client application
Next, you need to define the manifest of api applications and grant application permissions to your client applications (this is the role permissions you define yourself, you can find it in My APIs when you add permissions)
This is the process of defining the manifest.
This is to grant permissions for the client application (You can find your expose api permissions in My APIs.):
Request access token:
Parse the token:

Related

Authenticating an API request with a Token

Suppose I make an API request to the following URL:
https://myapi.com/data
This is an API I built and have full control over.
I would like to limit access to this API to only the apps I authorize.
I see many services will provide you with an API key which you can append to your URL to give you access.
Suppose I have:
https://myapi.com/data?key=a6reallly7long2string9of0numbers2and4letters
Then on the backend I have something like:
class REST {
public $ACCESS_TOKEN = 'a6reallly7long2string9of0numbers2and4letters',
public function Auth($token){
if($token===$this->ACCESS_TOKEN) return true;
return false;
}
}
If the values match, I allow access.
But all someone would have to do is look at the request the app is making on the client side and they have the token.
Even if I encrypt the token or use one-way hashing, they'll still have the value that decrypts to the correct result.
How does one approach good authentication via URL token for an API?
I would like to limit access to this API to only the apps I authorize.
What are you looking for is "access authorization". Indeed, an access token seems to be a good way, but there are some aspects missing
** Authorization header **
By default the token should be sent as an HTTP header (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) not in the url
Commonly used Authorization headers types are Basic (for basic authentication) and Bearer (for OAuth authentication and authorization)
The token should not be a hardcoded constant. It should be created/generated based on the application (and optionally user) authentication. And still better if the token is temporary
Now you can ask - how can an application keep its credentials secret? Each application can have their own server services (end user should.not access application credentials) or pure web application should be comtrolled by the CORS headers
Just search for OAuth 2.0 protocol and JWT token (jwt should be self-contained and signed).
IMHO the URL token may be an option when there is no other alternative, as URL is often cached, resent, logged,...
** API Manager **
If you have resources (server) to do so, you can deploy an API manager (there are open source, commercial or cloud options, just search for some). API manager will handle the application enrollment, authorization and enforcement.

Integration tests for web api with Azure AD

I am working on a webapi webservice that is proteted by Azure Active Directory. The webservice cumminucates heavily with Office 365 (SharePoint / Yammer) based on the user that is signed in.
To test the web api endpoints I am writing an Console App that let me sign in with my AAD credentials and then calls the endpoints. It works, but looking for something to replace this way of testing the web api. Would be great if it’s more repeatable and that I don’t have to fill in my credentials each time. I was looking for a unit test project but can’t get the Azure AD sign in to work.
Any tips how to make this easier?
The easiest way would be to define the test runner as an application in Azure AD and have it call the API with its own client id and secret.
To do that there are a few things you would need to do:
Add appRoles to your API in its manifest in Azure AD. These are application permissions.
Define your test runner, and have it require the necessary application permissions for your API.
In your test runner you should now be able to get an access token with the client id and secret of the test runner, no user authentication required.
Some setup is needed for app permissions on the API side as well, authorization must also look at the role claims.
You can find an example for defining app permissions and also handling them here: http://www.dushyantgill.com/blog/2014/12/10/roles-based-access-control-in-cloud-applications-using-azure-ad/.
More on defining app permissions: https://stackoverflow.com/a/27852592/1658906.
More info on the application manifest in AAD: https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-application-manifest.
EDIT: If you must make calls on behalf of the user in the API, then this of course won't work.
In that case, I would suggest creating a user account with the necessary access for the purpose of running the tests. It would be best not to hard-code its credentials, but store them elsewhere.
If you don't want to "fill in my credentials each time", one workaround is using the Resource Owner Password Credentials Grant flow. This flow is flexible to gain a token easily. In the Console App, you could directly use user account and password to get the access token for your protected web API . The code below is for your reference :
static void Main(string[] args)
{
test().Wait();
}
public static async Task test()
{
using (HttpClient client = new HttpClient())
{
var tokenEndpoint = #"https://login.windows.net/a703965c-e057-4bf6-bf74-1d7d82964996/oauth2/token";
var accept = "application/json";
client.DefaultRequestHeaders.Add("Accept", accept);
string postBody = #"resource=https%3A%2F%2Fgraph.microsoft.com%2F //here could be your own web api
&client_id=<client id>
&grant_type=password
&username=nanyu#xxxxxxx.onmicrosoft.com
&password=<password>
&scope=openid";
using (var response = await client.PostAsync(tokenEndpoint, new StringContent(postBody, Encoding.UTF8, "application/x-www-form-urlencoded")))
{
if (response.IsSuccessStatusCode)
{
var jsonresult = JObject.Parse(await response.Content.ReadAsStringAsync());
var token = (string)jsonresult["access_token"];
}
}
}
}
But the problem is that flow will expose the username and password directly in the code, it brings potential attack risk as well and we will always avoid handling the user credential directly. So make sure you just use this flow for testing in a secure environment. You could refer to this article for more details.

HTTP 500 using OAuthAuthentication with Azure Active Directory to secure ASP.NET 5 Web API

For context, I have OpenIdConnect with an ASP.NET 4 Web App working using Owin (and a lot of help from Modern Authentication with Azure Active Directory for Web Applications.
I now want to secure a separate ASP.NET 5 Web API project (to be hosted in the same AD tenant in Azure as a microservice). I started with the simple ASP.NET 5 WebApi generated in Visual Studio and added the following to the Configure in Startup.cs (at the beginning of the pipeline):
app.UseOAuthAuthentication(new OAuthOptions()
{
ClientId = "71d33a1c-505c-4815-a790-8494dd2bb430",
ClientSecret = "LajQFbf1/Nyt/6zCP5vE5YWj5VC4aNaC3i/SRtEj2sI=",
TokenEndpoint = "https://login.microsoftonline.com/7058f4f0-619f-4c16-ac31-9e209d70ff23/oauth2/token",
AuthorizationEndpoint = "https://login.microsoftonline.com/7058f4f0-619f-4c16-ac31-9e209d70ff23/oauth2/authorize",
AuthenticationScheme = "OAuth2Bearer",
CallbackPath = "/api/values"
});
This gives me an error that indicates SignInScheme must be provided, but I'm not clear on what that value should be. If I add in a string, say "OAuth2Bearer", I get further, but still get a 500 error on the request, but no exception raised in the API app, nor does the breakpoint on the first line in my API controller implementation get hit.
What am I missing? Ideally, I want to then extend the Events of OAuthOptions to add a custom claim, analogous to what I did with OpenIdConnect and the SecurityTokenValidated notification.
The OAuth2 base middleware cannot be used for token validation as it's an OAuth2 client middleware made for handling interactive flows like the authorization code flow. All the existing OAuth2 social providers (e.g Facebook or Google) inherit from this middleware.
You're actually looking for the JWT bearer middleware:
app.UseJwtBearerAuthentication(options => {
options.AutomaticAuthenticate = true;
options.AutomaticChallenge = true;
options.Authority = "[address of your OIDC server]";
options.Audience = "[audience of the access tokens issued by the OIDC server]";
});
To learn more about the different OAuth2/OIDC middleware in ASP.NET Core, don't miss this other SO post: Configure the authorization server endpoint.

OWIN Authentication Server for multiple applications

I am in the process of implementing a solution that has an MVC client (lets call this CLIENT at localhost:4077/) with a WebAPI service (called API at localhost:4078/)
I have implemented OWIN OAuth in the API but wanted to know whether the OWIN could be implemented in a separate solution (lets call it AUTH at localhost:4079/token) to generate the token for the CLIENT, then the CLIENT passes this to the API (as the Bearer authorisation token)
The reason i am querying this is that there is likely to be additional WebAPI services that will be accessed by the CLIENT and i'd like to use OWIN between the client and all API services.
The issue is i am not sure if the token generated by the AUTH service could be used to authorise all requests on the CLIENT and all API services.
Has anyone implemented anything like this and if so could you provide an example, i am pretty new to OWIN and OAUTH so any help would be greatly appreciated
Separating the authorization server from the resource server is extremely easy: it will even work without any extra code if you use IIS and if you have configured identical machine keys on both applications/servers.
Supporting multiple resource servers is a bit harder to implement with the OWIN OAuth2 server if you need to select which endpoints an access token can gain access to. If you don't care about that, just configure all your resource servers with the same machine keys, and you'll be able to access all your APIs with the same tokens.
To have more control over the endpoints that can be used with an access token, you should take a look at AspNet.Security.OpenIdConnect.Server - a fork of the OAuth2 server that comes with OWIN/Katana - that natively supports this scenario: https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server.
It's relatively easy to set up:
Add a new middleware issuing tokens in your authorization server application (in Startup.cs):
app.UseOpenIdConnectServer(new OpenIdConnectServerOptions
{
Provider = new AuthorizationProvider()
});
Add new middleware validating access tokens in your different API servers (in Startup.cs):
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
{
// AllowedAudiences MUST contain the absolute URL of your API.
AllowedAudiences = new[] { "http://localhost:11111/" },
// X509CertificateSecurityTokenProvider MUST be initialized with an issuer corresponding to the absolute URL of the authorization server.
IssuerSecurityTokenProviders = new[] { new X509CertificateSecurityTokenProvider("http://localhost:50000/", certificate) }
});
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
{
// AllowedAudiences MUST contain the absolute URL of your API.
AllowedAudiences = new[] { "http://localhost:22222/" },
// X509CertificateSecurityTokenProvider MUST be initialized with an issuer corresponding to the absolute URL of the authorization server.
IssuerSecurityTokenProviders = new[] { new X509CertificateSecurityTokenProvider("http://localhost:50000/", certificate) }
});
Finally, add a new OpenID Connect client middleware in your client app (in Startup.cs):
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
// Some essential parameters have been omitted for brevity.
// See https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/blob/dev/samples/Mvc/Mvc.Client/Startup.cs for more information
// Authority MUST correspond to the absolute URL of the authorization server.
Authority = "http://localhost:50000/",
// Resource represents the different endpoints the
// access token should be issued for (values must be space-delimited).
// In this case, the access token will be requested for both APIs.
Resource = "http://localhost:11111/ http://localhost:22222/",
});
You can have a look at this sample for more information: https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/blob/dev/samples/Mvc/
It doesn't use multiple resource servers, but it shouldn't be hard to adapt it using the different steps I mentioned. Feel free to ping me if you need help.

WAAD Authentication with WebAPI OData service consumed by Excel PowerQuery

I've created a WebAPI OData 3.0 web service with an OWIN middleware, which is configured for authentication with Windows Azure Active Directory.
The ODataControllers are marked with an [Authorize] attribute, and the IAppBuilder is configured as follows:
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters {
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
},
});
ida:Tenant is my Windows Azure tenancy, and ida:Audience is the App ID Uri.
Now I would like to consume this service using Excel PowerQuery, authenticating using an account from the AzureAD. However, when I choose "Organizational account" and try to "Sign in", I get the following error:
Unable to connect. This credential type is not supported for this resource.
In Fiddler I can see that the request is made with a Bearer header, but it is empty.
I would like to achieve a behavior similar to when querying AzureAD Graph.
For example, if I try to consume https://graph.windows.net/.onmicrosoft.com/users?api-version=2013-04-05, a single sign-on window opens, and in Fiddler I can see that a token is passed.
How can I achieve this behavior? what am I missing?
Thanks!
Here is the expected flow between PowerQuery and an OData service during authentication:
When you enter the URI to your service in the builder, click ok, you will get a credential prompt asking for your credentials to access the service.
Typically, you would choose Organizational Account if Azure Active Directory (AAD) is your Identity Provider.
When you click sign in, PowerQuery will send a challenge request to your service, which is the empty bearer you are seeing. The reason is, we don't know what's your identity provider or where should we log you in, the request is expecting a 401/403 response with a WWW-Authenticate header that has the authentication endpoint url.
Here is the expected header format:WWW-Authenticate authorization_uri=”token service uri” quotes are optional. If we don't find that header, you get the error message 'Unable to connect. This credential type is not supported'.
For example, in your scenario, the token service uri is https://login.windows.net
When we receive that response, we will get the url from the header and issue a login request, at which point you will see the login page from AAD and you will be able to login using your organizational credentials.
We will wait for the sign in result, which should be a token, that token will be used to fill in the bearer in the header at anytime you request data from that service.
There are two important things regarding your application object in AAD to make this work:
The AppIdUris property has to have a wildcard URI that would match with your service URI. When we send the login request we have to include a resource id, the resource is the authority of the service we are connecting to. So if your service url is: myservice.com/myODatafeed.svc, the authority includes the scheme, the host and the port number, myservice.com/ would be the authority. For services that might have different tenants for example: company1.myservice.com, the AppIdUri has to have https://*.myservice.com. Otherwise, just https://myservice.com.
The second thing (and this on is AAD specific), AAD doesn't support first party client (PowerQuery) to third party service (your service) authentication today. but hopefully soon enough :) Maybe just when you get the rest done :)!
Update: This has been enabled in the latest release of PowerQuery. Your app will need to expose the user_imperonation scope, and you are good to go :)!

Resources