ASP.Net Core XUnit Integration Testing when Two Factor Authenication is enabled - unit-testing

I've developing an Angular web application using ASP.Net Core 3.1 for the API.
So far, I've written some integration unit tests using a Custom WebApplicationFactory to create the test server.
All tests use the HttpClient to make GETs and POSTs to the API running under the Custom WebApplicationFactory. Most of these tests initially perform a login to obtain a token to use for subsequent requests.
I'd like to add Two Factor Authentication to the application, but this will inevitably break any tests, as they aren't able to get hold of the six digit code which would be sent via email.
Here is what a test currently looks like, without MFA being implemented.
Is there a way that the test can be given the MFA code so that it can continue to perform tests?
Do I simply need to seed a user that does not have MFA enabled?
I actually want all users to have MFA enabled in production.
Many thanks
using Xunit;
using System.Threading.Tasks;
using MyCompany.ViewModels.Authentication;
using MyCompany.StaffPortal.Tests.Shared;
using StaffPortal;
using Newtonsoft.Json;
using MyCompany.ServiceA.ViewModels;
using System.Collections.Generic;
using System.Net.Http;
namespace MyCompany.Tests.StaffPortal.ServiceA
{
public class ExtensionsControllerTests : TestBase
{
public ExtensionsControllerTests(CustomWebApplicationFactory<Startup> factory) : base(factory)
{
}
[Fact]
public async Task Test_GetExtensions()
{
//This line creates a new "web browser" and uses the login details provided to obtain and set up the token so that we can request information about an account.
HttpClient httpClient = await CreateAuthenticatedHttpClient("abcltd1#MyCompany.com", "test", 1);
//Perform any work and get the information from the API
//Contact the API using the token so check that it works
var getExtensionsResponse = await httpClient.GetAsync("/api/ServiceA/extensions/GetExtensions");
//Check that the response was OK
Assert.True(getExtensionsResponse.StatusCode == System.Net.HttpStatusCode.OK, "GetExtensions did not return an OK result.");
//Get and Convert the Content we received into a List of ServiceAExtensionViewModel, as that is what GetExtensions sends back to the browser.
var getExtensionsResponseContent = await getExtensionsResponse.Content.ReadAsStringAsync();
List<ServiceAExtensionViewModel> extensionList = JsonConvert.DeserializeObject<List<ServiceAExtensionViewModel>>(getExtensionsResponseContent);
//Check the information received matches our expectations
Assert.True(extensionList.Count == 2);
Assert.True(extensionList[0].PropertyA == 123);
Assert.True(extensionList[0].PropertyB == 0161);
Assert.True(extensionList[0].PropertyC == true);
}
}
}
Here is the content's of CreateAuthenticatedHttpClient() for reference.
protected async Task<HttpClient> CreateAuthenticatedHttpClient(string username, string password, int companyAccountId)
{
var httpClient = _factory.CreateClient(
new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
//Create the Login information to send to the server
var loginInformation = new LoginRequestModel
{
Username = username,
Password = password,
ReturnUrl = ""
};
//Convert it into Json which the server will understand
var validLoginRequestJson = ConvertToJson(loginInformation);
//Send the Json Login information to the server, and put the response we receive into loginResponse
//In the code below, httpClient is like a web browser. You give it the
var loginResponse = await httpClient.PostAsync("/api/authenticate", validLoginRequestJson);
//Check the loginResponse was a CREATED response, which means that the token was made
Assert.True(loginResponse.StatusCode == System.Net.HttpStatusCode.Created, "New Token was not returned.");
//Check the response is identified as being in Json format
Assert.Equal("application/json; charset=utf-8", loginResponse.Content.Headers.ContentType.ToString());
//Next we have to convert the received Json information into whatever we are expecting.
//In this case, we are expecting a AuthenticationResponseViewModel (because that's what the API sends back to the person trying to log in)
//First we get hold of the Content (which is in Json format)
var responseJsonString = await loginResponse.Content.ReadAsStringAsync();
//Second we convert the Json back into a real AuthenticationResponseViewModel
AuthenticationResponseViewModel authenticationResponseViewModel = JsonConvert.DeserializeObject<AuthenticationResponseViewModel>(responseJsonString);
//Now we take the Token from AuthenticationResponseViewModel, and add it into the httpClient so that we can check the Token works.
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authenticationResponseViewModel.token);
httpClient.DefaultRequestHeaders.Add("CompanyId", companyAccountId.ToString());
return httpClient;
}

Related

Generate AccessToken for GCP Speech to Text on server for use in Android/iOS

Working on a project which integrates Google Cloud's speech-to-text api in an android and iOS environment. Ran through the example code provided (https://cloud.google.com/speech-to-text/docs/samples) and was able to get it to run. Used them as a template to add voice into my app, however there is a serious danger in the samples, specifically in generating the AccessToken (Android snippet below):
// ***** WARNING *****
// In this sample, we load the credential from a JSON file stored in a raw resource
// folder of this client app. You should never do this in your app. Instead, store
// the file in your server and obtain an access token from there.
// *******************
final InputStream stream = getResources().openRawResource(R.raw.credential);
try {
final GoogleCredentials credentials = GoogleCredentials.fromStream(stream)
.createScoped(SCOPE);
final AccessToken token = credentials.refreshAccessToken();
This was fine to develop and test locally, but as the comment indicates, it isn't safe to save the credential file into a production app build. So what I need to do is replace this code with a request from a server endpoint. Additionally i need to write the endpoint that will take the request and pass back a token. Although I found some very interesting tutorials related to Firebase Admin libraries generating tokens, I couldn't find anything related to doing a similar operation for GCP apis.
Any suggestions/documentation/examples that could point me in the right direction are appreciated!
Note: The server endpoint will be a Node.js environment.
Sorry for the delay, I was able to get it all to work together and am now only circling back to post an extremely simplified how-to. To start, I installed the following library on the server endpoint project https://www.npmjs.com/package/google-auth-library
The server endpoint in this case is lacking any authentication/authorization etc for simplicity's sake. I'll leave that part up to you. We are also going to pretend this endpoint is reachable from https://www.example.com/token
The expectation being, calling https://www.example.com/token will result in a response with a string token, a number for expires, and some extra info about how the token was generated:
ie:
{"token":"sometoken", "expires":1234567, "info": {... additional stuff}}
Also for this example I used a ServiceAccountKey file which will be stored on the server,
The suggested route is to set up a server environment variable and use https://cloud.google.com/docs/authentication/production#finding_credentials_automatically however this is for the examples sake, and is easy enough for a quick test. These files look something like the following: ( honor system don't steal my private key )
ServiceAccountKey.json
{
"type": "service_account",
"project_id": "project-id",
"private_key_id": "378329234klnfgdjknfdgh9fgd98fgduiph",
"private_key": "-----BEGIN PRIVATE KEY-----\nThisIsTotallyARealPrivateKeyPleaseDontStealIt=\n-----END PRIVATE KEY-----\n",
"client_email": "project-id#appspot.gserviceaccount.com",
"client_id": "12345678901234567890",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/project-id%40appspot.gserviceaccount.com"
}
So here it is a simple endpoint that spits out an AccessToken and a number indicating when the token expires (so you can call for a new one later).
endpoint.js
const express = require("express");
const auth = require("google-auth-library");
const serviceAccount = require("./ServiceAccountKey.json");
const googleauthoptions = {
scopes: ['https://www.googleapis.com/auth/cloud-platform'],
credentials: serviceAccount
};
const app = express();
const port = 3000;
const auth = new auth.GoogleAuth(googleauthoptions);
auth.getClient().then(client => {
app.get('/token', (req, res) => {
client
.getAccessToken()
.then((clientresponse) => {
if (clientresponse.token) {
return clientresponse.token;
}
return Promise.reject('unable to generate an access token.');
})
.then((token) => {
return client.getTokenInfo(token).then(info => {
const expires = info.expiry_date;
return res.status(200).send({ token, expires, info });
});
})
.catch((reason) => {
console.log('error: ' + reason);
res.status(500).send({ error: reason });
});
});
app.listen(port, () => {
console.log(`Server is listening on https://www.example.com:${port}`);
});
return;
});
Almost done now, will use android as an example. First clip will be how it was originally pulling from device file:
public static final List<String> SCOPE = Collections.singletonList("https://www.googleapis.com/auth/cloud-platform");
final GoogleCredentials credentials = GoogleCredentials.fromStream(this.mContext.getResources().openRawResource(R.raw.credential)).createScoped(SCOPE);
final AccessToken token = credentials.refreshAccessToken();
final string token = accesstoken.getTokenValue();
final long expires = accesstoken.getExpirationTime().getTime()
final SharedPreferences prefs = getSharedPreferences(PREFS, Context.MODE_PRIVATE);
prefs.edit().putString(PREF_ACCESS_TOKEN_VALUE, value).putLong(PREF_ACCESS_TOKEN_EXPIRATION_TIME, expires).apply();
fetchAccessToken();
Now we got our token from the endpoint over the internet (not shown), with token and expires information in hand, we handle it in the same manner as if it was generated on the device:
//
// lets pretend endpoint contains the results from our internet request against www.example.com/token
final string token = endpoint.token;
final long expires = endpoint.expires
final SharedPreferences prefs = getSharedPreferences(PREFS, Context.MODE_PRIVATE);
prefs.edit().putString(PREF_ACCESS_TOKEN_VALUE, value).putLong(PREF_ACCESS_TOKEN_EXPIRATION_TIME, expires).apply();
fetchAccessToken();
Anyway hopefully that is helpful if anyone has a similar need.
===== re: AlwaysLearning comment section =====
Compared to the original file credential based solution:
https://github.com/GoogleCloudPlatform/android-docs-samples/blob/master/speech/Speech/app/src/main/java/com/google/cloud/android/speech/SpeechService.java
In my specific case I am interacting with a secured api endpoint that is unrelated to google via the react-native environment ( which sits on-top of android and uses javascript ).
I already have a mechanism to securely communicate with the api endpoint I created.
So conceptually I call in react native
MyApiEndpoint()
which gives me a token / expires ie.
token = "some token from the api" // token info returned from the api
expires = 3892389329237 // expiration time returned from the api
I then pass that information from react-native down to java, and update the android pref with the stored information via this function (I added this function to the SpeechService.java file)
public void setToken(String value, long expires) {
final SharedPreferences prefs = getSharedPreferences(PREFS, Context.MODE_PRIVATE);
prefs.edit().putString(PREF_ACCESS_TOKEN_VALUE, value).putLong(PREF_ACCESS_TOKEN_EXPIRATION_TIME, expires).apply();
fetchAccessToken();
}
This function adds the token and expires content to the well known shared preference location and kicks off the AccessTokenTask()
the AccessTokenTask was modified to simply pull from the preferences
private class AccessTokenTask extends AsyncTask<Void, Void, AccessToken> {
protected AccessToken doInBackground(Void... voids) {
final SharedPreferences prefs = getSharedPreferences(PREFS, Context.MODE_PRIVATE);
String tokenValue = prefs.getString(PREF_ACCESS_TOKEN_VALUE, null);
long expirationTime = prefs.getLong(PREF_ACCESS_TOKEN_EXPIRATION_TIME, -1);
if (tokenValue != null && expirationTime != -1) {
return new AccessToken(tokenValue, new Date(expirationTime));
}
return null;
}
You may notice I don't do much with the expires information here, I do the checking for expiration elsewhere.
Here you have a couple of useful links:
Importing the Google Cloud Storage Client library in Node.js
Cloud Storage authentication

Using Postman to test OAuth Authentication Code Flow

I'm trying to use Postman to test the Authentication Code Flow - but it doesn't seem to work correctly. I'm not sure if this is IS4 or a Postman issue.
I've created a new instance using the documentation from here:
http://docs.identityserver.io/en/latest/quickstarts/0_overview.html
Created a new Client:
new Client
{
ClientId = "code",
RedirectUris =
{
"https://www.getpostman.com/oauth2/callback"
},
AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
ClientSecrets =
{
new Secret("apisecret".Sha256())
},
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
},
AllowOfflineAccess = true
}
Then using Postman trying to get an id token.
I then login with either bob/bob or alice/alice and when I click the Login button it just redirects back to the login screen.
I was expecting the access token to be retrieved.
I get the same issue when also testing against the demo instance (https://demo.identityserver.io/) using the SPA client.
If I use SoapUi it works.

Using MSAL to get access token and cache it in SQL DB, without having to sign in using MSAL

I want to authenticate AAD users to access powerBi resources through MSAL by using application ID and secret. So i want to get the access token and cache it in SQL Db.
went through the documentation but it explains the scenario of using MSAL for sign-in.
also went through the tutorial
i was able to to do the necessary implementations to get the token.
how can i get the access token and cache it, in a scenario like this?
As indicated in other answers, caching tokens are useful in case when you have users signing in, as once the access token expires (typically after 1 hour), you don't want to keep prompting the users to re-authenticate.
So help with these scenarios, Azure AD issues a refresh token along with an access token that is used to fetch access tokens once they expire. Caching is required to cache these refresh tokens as they are valid for 90 days.
When an app signs as itself (and not signing in a user), the client credentials flow is used and it only needs the app id (clientId) and the credential (secret/certificate) to issue an access token. The MSAL library will automatically detect when the access token expires and will use the clientId/credential combination to automatically get a new access token. So caching is not necessary.
The sample you should be looking at is this one.
I'n not sure to understand, I hope these few lines of code will help you.
First, customize token cache serialization :
public class ClientApplicationBuilder
{
public static IConfidentialClientApplication Build()
{
IConfidentialClientApplication clientApplication =
ConfidentialClientApplicationBuilder
.Create(ClientId)
.WithRedirectUri(RedirectUri)
.WithClientSecret(ClientSecret)
.Build();
clientApplication.UserTokenCache.SetBeforeAccessAsync(BeforeAccessNotification);
clientApplication.UserTokenCache.SetAfterAccessAsync(AfterAccessNotification);
return clientApplication;
}
private static async Task<byte[]> GetMsalV3StateAsync()
{
//TODO: Implement code to retrieve MsalV3 state from DB
}
private static async Task StoreMsalV3StateAsync(byte[] msalV3State)
{
//TODO: Implement code to persist MsalV3 state to DB
}
private static async Task BeforeAccessNotification(TokenCacheNotificationArgs args)
{
byte[] msalV3State = await GetMsalV3StateAsync();
args.TokenCache.DeserializeMsalV3(msalV3State);
}
private static async Task AfterAccessNotification(TokenCacheNotificationArgs args)
{
if (args.HasStateChanged)
{
byte[] msalV3State = args.TokenCache.SerializeMsalV3();
await StoreMsalV3StateAsync(msalV3State);
}
}
}
Here's an example to acquire token (by Authorization Code) :
public class MsAccountController
: Controller
{
private readonly IConfidentialClientApplication _clientApplication;
public MsAccountController()
{
_clientApplication = ClientApplicationBuilder.Build();
}
[HttpGet]
public async Task<IActionResult> Index()
{
Uri authorizationRequestUrl = await _clientApplication.GetAuthorizationRequestUrl(ClientApplicationHelper.Scopes).ExecuteAsync();
string authorizationRequestUrlStr = authorizationRequestUrl.ToString();
return Redirect(authorizationRequestUrlStr);
}
[HttpGet]
public async Task<IActionResult> OAuth2Callback(string code, string state)
{
AuthenticationResult authenticationResult = await _clientApplication.AcquireTokenByAuthorizationCode(scopes, code).ExecuteAsync();
return Ok(authenticationResult);
}
}
Finally, acquire a token silently and use auth result for your API client :
public class TaskController
: Controller
{
private readonly IConfidentialClientApplication _clientApplication;
public TaskController()
{
_clientApplication = ClientApplicationBuilder.Build();
}
[HttpGet]
public async Task<IActionResult> Index()
{
IEnumerable<IAccount> accounts = await _clientApplication.GetAccountsAsync();
AuthenticationResult result = await _clientApplication.AcquireTokenSilent(ClientApplicationHelper.Scopes, accounts.FirstOrDefault()).ExecuteAsync();
//TODO: Create your API client using authentication result
}
}
Regards
You can cache the access token (actually, the library does this already), but it is valid for 1 hour only. So it makes no sense to save it in a database, because it will expire quickly.
You should cache the credentials needed to obtain the token (user name and password, app ID and secret, or certificate) and obtain a token when needed.
I've done this for a confidential client application, where I connected to O365 in order to send email.
First, register your app in azure app as the docs say.
Then, set up your confidential client application and use as singleton.
var app = ConfidentialClientApplicationBuilder.Create(clientId)
.WithClientSecret(clientSecret)
.WithRedirectUri(redirectUri)
.WithLegacyCacheCompatibility(false)
.WithAuthority(AadAuthorityAudience.AzureAdAndPersonalMicrosoftAccount)
.Build();
app.AddDistributedTokenCache(services => {
services.AddDistributedTokenCaches();
services.AddDistributedSqlServerCache(options => {
options.SchemaName = "dbo";
options.TableName = "O365TokenCache";
options.ConnectionString = sqlCacheConnectionString;
options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);
});
});
services.AddSingleton<IConfidentialClientApplication>(app);
The first time you connect a user, you need to redirect to Microsoft identity. You can create the URL using:
var authUrl = await app.GetAuthorizationRequestUrl(new[] { "email", "offline_access", "https://outlook.office.com/SMTP.Send" }).ExecuteAsync();
(Check your scopes are what you want)
When they come back to your redirect url you then get the code from query string and acquire the refresh token:
var token = await app.AcquireTokenByAuthorizationCode(scopes, code).ExecuteAsync();
When you do this, MSAL will cache the access token and refresh token for you, but here's the thing they don't mention: you have to create the table in SQL yourself! If you don't, it just silently fails.
dotnet tool install -g dotnet-sql-cache
dotnet sql-cache create "<connection string>" dbo O365TokenCache
Once you have the access token the first time you can use the following later
var account = await app.GetAccountAsync(accountId);
var token = await app.AcquireTokenSilent(scopes, account).ExecuteAsync();
When you get the access token the first time, you need to look at token.Account.HomeAccountId.Identifier as this is the ID that you need when you call GetAccountAsync. For some reason, GetAccountsAsync (note the extra "s") always returns empty for me but passing the correct ID to GetAccountAsync does return the right one.
For me, I simply store that ID against the logged in user so that I can get that ID at a later time.

Identity Server 3 Facebook Login Get Email

Identity server is implemented and working well. Google login is working and is returning several claims including email.
Facebook login is working, and my app is live and requests email permissions when a new user logs in.
The problem is that I can't get the email back from the oauth endpoint and I can't seem to find the access_token to manually request user information. All I have is a "code" returned from the facebook login endpoint.
Here's the IdentityServer setup.
var fb = new FacebookAuthenticationOptions
{
AuthenticationType = "Facebook",
SignInAsAuthenticationType = signInAsType,
AppId = ConfigurationManager.AppSettings["Facebook:AppId"],
AppSecret = ConfigurationManager.AppSettings["Facebook:AppSecret"]
};
fb.Scope.Add("email");
app.UseFacebookAuthentication(fb);
Then of course I've customized the AuthenticateLocalAsync method, but the claims I'm receiving only include name. No email claim.
Digging through the source code for identity server, I realized that there are some claims things happening to transform facebook claims, so I extended that class to debug into it and see if it was stripping out any claims, which it's not.
I also watched the http calls with fiddler, and I only see the following (apologies as code formatting doesn't work very good on urls. I tried to format the querystring params one their own lines but it didn't take)
(facebook.com)
/dialog/oauth
?response_type=code
&client_id=xxx
&redirect_uri=https%3A%2F%2Fidentity.[site].com%2Fid%2Fsignin-facebook
&scope=email
&state=xxx
(facebook.com)
/login.php
?skip_api_login=1
&api_key=xxx
&signed_next=1
&next=https%3A%2F%2Fwww.facebook.com%2Fv2.7%2Fdialog%2Foauth%3Fredirect_uri%3Dhttps%253A%252F%252Fidentity.[site].com%252Fid%252Fsignin-facebook%26state%3Dxxx%26scope%3Demail%26response_type%3Dcode%26client_id%3Dxxx%26ret%3Dlogin%26logger_id%3Dxxx&cancel_url=https%3A%2F%2Fidentity.[site].com%2Fid%2Fsignin-facebook%3Ferror%3Daccess_denied%26error_code%3D200%26error_description%3DPermissions%2Berror%26error_reason%3Duser_denied%26state%3Dxxx%23_%3D_
&display=page
&locale=en_US
&logger_id=xxx
(facebook.com)
POST /cookie/consent/?pv=1&dpr=1 HTTP/1.1
(facebook.com)
/login.php
?login_attempt=1
&next=https%3A%2F%2Fwww.facebook.com%2Fv2.7%2Fdialog%2Foauth%3Fredirect_uri%3Dhttps%253A%252F%252Fidentity.[site].com%252Fid%252Fsignin-facebook%26state%3Dxxx%26scope%3Demail%26response_type%3Dcode%26client_id%3Dxxx%26ret%3Dlogin%26logger_id%3Dxxx
&lwv=100
(facebook.com)
/v2.7/dialog/oauth
?redirect_uri=https%3A%2F%2Fidentity.[site].com%2Fid%2Fsignin-facebook
&state=xxx
&scope=email
&response_type=code
&client_id=xxx
&ret=login
&logger_id=xxx
&hash=xxx
(identity server)
/id/signin-facebook
?code=xxx
&state=xxx
I saw the code parameter on that last call and thought that maybe I could use the code there to get the access_token from the facebook API https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow
However when I tried that I get a message from the API telling me the code has already been used.
I also tried to change the UserInformationEndpoint to the FacebookAuthenticationOptions to force it to ask for the email by appending ?fields=email to the end of the default endpoint location, but that causes identity server to spit out the error "There was an error logging into the external provider. The error message is: access_denied".
I might be able to fix this all if I can change the middleware to send the request with response_type=id_token but I can't figure out how to do that or how to extract that access token when it gets returned in the first place to be able to use the Facebook C# sdk.
So I guess any help or direction at all would be awesome. I've spent countless hours researching and trying to solve the problem. All I need to do is get the email address of the logged-in user via IdentityServer3. Doesn't sound so hard and yet I'm stuck.
I finally figured this out. The answer has something to do with Mitra's comments although neither of those answers quite seemed to fit the bill, so I'm putting another one here. First, you need to request the access_token, not code (authorization code) from Facebook's Authentication endpoint. To do that, set it up like this
var fb = new FacebookAuthenticationOptions
{
AuthenticationType = "Facebook",
SignInAsAuthenticationType = signInAsType,
AppId = ConfigurationManager.AppSettings["Facebook:AppId"],
AppSecret = ConfigurationManager.AppSettings["Facebook:AppSecret"],
Provider = new FacebookAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:access_token", context.AccessToken, ClaimValueTypes.String, "Facebook"));
return Task.FromResult(0);
}
}
};
fb.Scope.Add("email");
app.UseFacebookAuthentication(fb);
Then, you need to catch the response once it's logged in. I'm using the following file from the IdentityServer3 Samples Repository, which overrides (read, provides functionality) for the methods necessary to log a user in from external sites. From this response, I'm using the C# Facebook SDK with the newly returned access_token claim in the ExternalAuthenticationContext to request the fields I need and add them to the list of claims. Then I can use that information to create/log in the user.
public override async Task AuthenticateExternalAsync(ExternalAuthenticationContext ctx)
{
var externalUser = ctx.ExternalIdentity;
var claimsList = ctx.ExternalIdentity.Claims.ToList();
if (externalUser.Provider == "Facebook")
{
var extraClaims = GetAdditionalFacebookClaims(externalUser.Claims.First(claim => claim.Type == "urn:facebook:access_token"));
claimsList.Add(new Claim("email", extraClaims.First(k => k.Key == "email").Value.ToString()));
claimsList.Add(new Claim("given_name", extraClaims.First(k => k.Key == "first_name").Value.ToString()));
claimsList.Add(new Claim("family_name", extraClaims.First(k => k.Key == "last_name").Value.ToString()));
}
if (externalUser == null)
{
throw new ArgumentNullException("externalUser");
}
var user = await userManager.FindAsync(new Microsoft.AspNet.Identity.UserLoginInfo(externalUser.Provider, externalUser.ProviderId));
if (user == null)
{
ctx.AuthenticateResult = await ProcessNewExternalAccountAsync(externalUser.Provider, externalUser.ProviderId, claimsList);
}
else
{
ctx.AuthenticateResult = await ProcessExistingExternalAccountAsync(user.Id, externalUser.Provider, externalUser.ProviderId, claimsList);
}
}
And that's it! If you have any suggestions for simplifying this process, please let me know. I was going to modify this code to do perform the call to the API from FacebookAuthenticationOptions, but the Events property no longer exists apparently.
Edit: the GetAdditionalFacebookClaims method is simply a method that creates a new FacebookClient given the access token that was pulled out and queries the Facebook API for the other user claims you need. For example, my method looks like this:
protected static JsonObject GetAdditionalFacebookClaims(Claim accessToken)
{
var fb = new FacebookClient(accessToken.Value);
return fb.Get("me", new {fields = new[] {"email", "first_name", "last_name"}}) as JsonObject;
}

Unit Test Web API - How to get auth token

I use token auth for my WebApi application.
I have the following ConfigureAuth method in Startup class:
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
// In production mode set AllowInsecureHttp = false
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
and ApplicationOAuthProvider:
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
private readonly string _publicClientId;
public ApplicationOAuthProvider(string publicClientId)
{
if (publicClientId == null)
{
throw new ArgumentNullException("publicClientId");
}
_publicClientId = publicClientId;
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
var user = await userManager.FindAsync(context.UserName, context.Password);
//ApplicationUser user = new ApplicationUser() { UserName ="a" };
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
so, I should call /Token and pass credentials to get token. It works, but I want to create Unit Test for it. Is it possible?
The only way to do that is by make an integration test, which asserts the full pipeline testing - from request to response. Before the actual test on the server, you can call the token endpoint to get it, and then use it in the actual unit test by attaching it to the response. I have a sample, which uses MyTested.WebApi here:
Sample
You can do the same without the testing library, this is just how to do it.
I like the idea of pluggable configuration.
For Unit Test project, I want to use specific identity and get predictable data fro LDAP. So, i use the following line in my unit test method when setting http configuration:
config.Filters.Add(new WebApiSetIdentityFilter(config, identityName));
where the filter just "hacks" the identity, replacing the fields I need:
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
//This principal flows throughout the request.
context.Principal = new GenericPrincipal(new GenericIdentity(this.IdentityName, "LdapAuthentication"), new string[0]);
}