Integration tests for web api with Azure AD - asp.net-web-api

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.

Related

What URL do I use to send users to google oauth2 consent screen

I am trying to write a simple application to access google's api using user authentication tokens and html requests, however I am struggling to find what URL I send users too in order for them to select a profile and sign in.
URL I send users too in order for them to select a profile and sign in.
The thing is you are confusing authorization and authentication. Oauth2 a user can authorize you to access their data, it has nothing to do with logging in to your application that's OpenID connect.
However what you are probably looking for is the oauth2 consent screen This is the screen where the user consents to your application accessing their data.
https://accounts.google.com/o/oauth2/auth?client_id={clientid}&redirect_uri={redirectURI}&scope={scope}&response_type=code
Remember this is only the first step if they consent then you will be given an authorization code your application must then exchange the authorization code for an access token which you can use to access the api.
You may find this video helpful in understanding the fill Oauth2 dance. Understanding Google OAuth 2.0 with curl
If you are looking to login a user and check their profile something like this would be better
[GoogleScopedAuthorize(PeopleServiceService.ScopeConstants.UserinfoProfile)]
public async Task UserProfile([FromServices] IGoogleAuthProvider auth)
{
var cred = await auth.GetCredentialAsync();
var service = new PeopleServiceService(new BaseClientService.Initializer()
{
HttpClientInitializer = cred
});
var request = service.People.Get("people/me");
request.PersonFields = "names";
var person = await request.ExecuteAsync();
return View(person);
}
The full tutorial and companion video can be found here Asp .net core 3 and Google login

Securing .NET Framework Web API with Azure AD (Client credentials flow)

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:

MVC Prompt Query Parameter

I want to override Azure AD SSO login for a MVC web application. I don't want to log other applications out in the process, but require login for security purposes. I am using OAuth2.0 with OIDC via Owin for authentication.
I am trying to use the prompt=login query parameter, which should theoretically do the trick. I found a Github reference to this being recently made available in Core but cannot trace how to do it in MVC5.2
Is it possible to do it in the Application Builder? I tried adding .WithExtraQueryParameters ("prompt=login") to the ConfidentialClientApplicationBuilder when getting the access code. No luck.
Is there another workaround if the code doesn't come out-of-the-box?
EDIT: PublicClientApplication allows .WithPrompt(Prompt.Login) while ConfidentialClientApplication does not (also does not allow AcquireTokenInteractive) This is a web app so it needs the confidential builder. Tested using the Public builder and it logs in successfully. But I get an ActiveX instantiation error 'not in a single-threaded apartment'(?) Strange, unless that is how the token is being delivered perhaps. I also tested by changing to multitenant in Azure and by toggling Public client on and off.
Any ideas?
You could use ForceLogin to add paramter to ConfidentialClientApplicationBuilder.
ForceLogin: enables the application developer to have the user prompted for credentials by the service even if this would not be needed. This can be useful if Acquiring a token fails, to let the user re-sign-in. This is done by sending prompt=login to the identity provider. Again, we've seen it used in some security focused applications where the organization governance demands that the user re-logs-in each time they access specific parts of an application.
So, use the code as below:
result = await app.AcquireTokenInteractive(scopes)
.WithPrompt(Prompt.ForceLogin)
.ExecuteAsync();
The Modifier .WithExtraQueryParameters will not help you. You need to use .WithPrompt instead.Please refer article.
Example:
await PublicClientApplication
.AcquireTokenInteractive(scopes, null)
.WithAccount(CurrentUser)
.WithPrompt(Prompt.ForceLogin) // use login for your case
.ExecuteAsync();
I eventually resolved this as follows:
Under Notifications in the ConfigureAuth(IAppBuilder app) add a reference to a new task:
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
Then add the task:
private Task OnRedirectToIdentityProvider(RedirectToIdentityProviderNotification
<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
// Forces the user to login.
if (notification.ProtocolMessage.Prompt == null)
{
notification.ProtocolMessage.Prompt = "login";
}
return Task.FromResult(0);
}
Hope that helps the next person with this issue.

Is it possible to use signed-in windows user credentials to authenticate to web API?

I am implementing authentication for a command line client application that makes a web request to a web API. If I reason correctly, I can apply Azure Active Directory native app authentication scenario.
My concern here is that setting up Azure AD will require significant effort from the client app users on setting up AAD, plus they will have to work with an interactive dialog. This gets even worse in case no human is present, as the service to service scenario is even more complicated.
Is it possible to instead rely on the credentials of the signed-in user of the client computer? Assume Windows-based client machine that is joined to a domain, say FooDomain. The server uses an OWIN-based self-host implementation, Katana.
Related questions:
OWIN Web API Windows Service - Windows Identity Impersonation
#Konrad Jamrozik. IF you are working on .NET and want to use the logged-in user in Windows domain joined (your case), and even AAD joined, my advice would be to use MSAL.NET with the Integrated Windows Authentication (IWA) override. See https://aka.ms/msal-net-iwa. The simplified code looks like this:
string authority = "https://login.microsoftonline.com/contoso.com";
string[] scopes = new string[] { "user.read" };
PublicClientApplication app = new PublicClientApplication(clientId, authority);
var accounts = await app.GetAccountsAsync();
AuthenticationResult result=null;
if (accounts.Any())
{
result = await app.AcquireTokenSilentAsync(scopes, accounts.FirstOrDefault());
}
else
{
result = await app.AcquireTokenByIntegratedWindowsAuthAsync(scopes);
}
This sample explains how to register the app and provides all the details about the code: https://github.com/azure-samples/active-directory-dotnet-iwa-v2

How to access Google API in combination with Azure AD single-sign on

I have a web application running on Azure. The web application authenticates the users via OpenID Connect from a Azure Active Directory tenant.
Azure Sample on GitHub.
On the Azure Active Directory tenant I have integrated Google Apps and configured single sing-on to Google Apps and automated user provisioning. Tutorial: How to integrate Google Apps with Azure Active Directory.
In my web application I would like to access user content from Google Apps (e.g. files on Google Drive) of the signed in user via Google API.
Is it possible to do this with the help of the setup single sign-on federation, so that the user only needs to sign in to the web application/Azure AD and for the Web API call there is no need for a further sign in, e.g. by using a token optained by Azure AD for accessing the Google Web API?
Tokens obtained from Azure AD cannot be used directly against Google API. However if you integrated Azure AD and Google Apps you should be able to go through the google token acquisition process without gathering user credentials again. You might want to go through an authorization code flow for getting tokens from google, and inject in the request information that would help to leverage your existing session. Typical examples are passing your user's UPN (via login_hint query parameter) and tenant (domain_hint). However I don't know if the google authorization endpoint will pass those along, you'll need to consult the google api documentation.
I ended up with two solutions:
a) Service Account:
Accessing the users data with a service account on behalf of a user.
For this you have to setup a service account: Using OAuth 2.0 for Server to Server Applications
private static ServiceAccountCredential GetServiceAccountCredential(string user)
{
const string privateKey = "<PRIVATEKEY>";
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer("<SERVICEACOUNTEMAIL>")
{
Scopes = new[] {DriveService.Scope.Drive},
User = user
}.FromPrivateKey(privateKey));
return credential;
}
b) User:
Accessing the users data with the user. For this you have to register your app to get the client ID and secret: Using OAuth 2.0 for Web Server Applications
private static UserCredential GetUserCredential(string user)
{
ClientSecrets secrets = new ClientSecrets
{
ClientId = "<CLIENTID>",
ClientSecret = "<CLIENTSECRET>"
};
IDataStore credentialPersistanceStore = new FileDataStore("Drive.Sample.Credentials");
Task<UserCredential> result = GoogleWebAuthorizationBroker.AuthorizeAsync(
secrets,
new[] {DriveService.Scope.Drive},
user,
CancellationToken.None,
credentialPersistanceStore);
result.Wait();
UserCredential credential = result.Result;
return credential;
}
With the credentials I can request the files from Drive:
Claim emailClaim = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Email);
IConfigurableHttpClientInitializer credential = GetServiceAccountCredential(emailClaim.Value);
//IConfigurableHttpClientInitializer credential = GetUserCredential(emailClaim.Value);
var service = new DriveService(new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = "My App"
});
FileList list = service.Files.List().Execute();
I am not yet sure which option I will use. Maybe you have some advices or suggestions.

Resources