I have found that the Web API template is broken - the default implementation relies on cookies in the final step, which you probably don't want to be using in a Rest API. Without a cookie, GetExternalLoginInfoAsync in RegisterExternal always returns null. I removed RegisterExternal entirely, instead creating the final user account in GetExternalLogin - called on return from the OAuth provider (in this case, Google):
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalCookie)]
[AllowAnonymous]
[Route("ExternalLogin", Name = "ExternalLogin")]
public async Task<IHttpActionResult> GetExternalLogin(string provider, string error = null)
{
if (error != null)
{
return Redirect(Url.Content("~/") + "#error=" + Uri.EscapeDataString(error));
}
if (!User.Identity.IsAuthenticated)
{
return new ChallengeResult(provider, this);
}
ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);
if (externalLogin == null)
{
return InternalServerError();
}
if (externalLogin.LoginProvider != provider)
{
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
return new ChallengeResult(provider, this);
}
ApplicationUser user = await UserManager.FindAsync(new UserLoginInfo(externalLogin.LoginProvider,
externalLogin.ProviderKey));
bool hasRegistered = user != null;
if (hasRegistered)
{
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(UserManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookieIdentity = await user.GenerateUserIdentityAsync(UserManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
Authentication.SignIn(properties, oAuthIdentity, cookieIdentity);
}
else
{
var accessToken = Authentication.User.Claims.Where(c => c.Type.Equals("urn:google:accesstoken")).Select(c => c.Value).FirstOrDefault();
Uri apiRequestUri = new Uri("https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + accessToken);
RegisterExternalBindingModel model = new RegisterExternalBindingModel();
using (var webClient = new System.Net.WebClient())
{
var json = webClient.DownloadString(apiRequestUri);
dynamic jsonResult = JsonConvert.DeserializeObject(json);
model.Email = jsonResult.email;
model.Picture = jsonResult.picture;
model.Family_name = jsonResult.family_name;
model.Given_name = jsonResult.given_name;
}
var appUser = new ApplicationUser
{
UserName = model.Email,
Email = model.Email,
ImageUrl = model.Picture,
FirstName = model.Given_name,
Surname = model.Family_name,
DateCreated = DateTime.Now
};
var loginInfo = await Authentication.GetExternalLoginInfoAsync();
IdentityResult result = await UserManager.CreateAsync(appUser);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
result = await UserManager.AddLoginAsync(appUser.Id, loginInfo.Login);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
ClaimsIdentity oAuthIdentity = await appUser.GenerateUserIdentityAsync(UserManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookieIdentity = await appUser.GenerateUserIdentityAsync(UserManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(appUser.UserName);
Authentication.SignIn(properties, oAuthIdentity, cookieIdentity);
}
return Ok();
}
This removes the need for the client application to perform a needless POST request after receiving an external access token.