Adding a Role to an IdentityUser .net core - asp.net-web-api

I have a set of API's that I want to put some authentication on. I have added the authorization and authentication pieces to the project. I have added the context for the database and the application user. I can create users and log them and in and return JWT to the caller and validate the users based on the JWT. However, there are certain users that I want to create that are admins that will have elevated privileges. Here is the code I am user to Create a user:
async Task<Response> ICreateUser.CreateUser(RegisterModel model)
{
var userExists = await userManager.FindByNameAsync(model.UserName);
if (userExists != null)
{
return new Response { Status = "error", Message = "User already exists" };
}
ApplicationUser user = new ApplicationUser()
{
Email = model.Email,
SecurityStamp = Guid.NewGuid().ToString(),
UserName = model.UserName
};
var result = await userManager.CreateAsync(user, model.Password);
if (!result.Succeeded)
{
return new Response { Status = "Error", Message = "Unable to create user" };
}
else
{
var adminRole = await roleManager.FindByNameAsync("admin");
if(!await userManager.IsInRoleAsync(user, adminRole.Name))
{
await userManager.AddToRoleAsync(user, adminRole.Name);
}
}
return new Response { Status = "Success", Message = "User Created" };
}
This will add the user, and even add them to the role. But when I do a list on the claims all I see are the nameidentifier, jti, email, exp, iss, and aud values. Here is the code I am using to return the claims:
public IActionResult Index()
{
var claims = User.Claims.Select(claim => new { claim.Type, claim.Value }).ToArray();
return Json(claims);
}
When I created the Role for the admin I used this code:
public async Task<IActionResult> Create([Required] string name)
{
var adminRole = await roleManager.FindByNameAsync(name);
if (adminRole == null)
{
adminRole = new IdentityRole(name);
await roleManager.CreateAsync(adminRole);
await roleManager.AddClaimAsync(adminRole, new Claim(ClaimTypes.Role, name));
return Ok("Role Created");
}
else
{
return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "error", Message = "Role Not created" });
}
}
Like I said, I can create the user, but I don't see the Admin claim added to user so I can't authenticate by the role. What am I missing?

When I created the JWT token I needed to add a call to GetRolesAsync. Once I got this list, I could loop through the array and add the claims.
var claim = new List<Claim>();
claim.Add(new Claim(JwtRegisteredClaimNames.Sub, user.UserName));
claim.Add(new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()));
claim.Add(new Claim(ClaimTypes.Email, model.Email));
var roles = userManager.GetRolesAsync(user).Result.ToArray();
foreach(var role in roles)
{
claim.Add(new Claim(ClaimTypes.Role, role));
}
This bit of code allows me to add the user to the roles they are assigned to when they are created.

Related

How to covert Json result into string in Blazor WebAssembly?

I want to convert the result into a string and pass it to the navigation path, but I couldn't do it, please help me.
HttpGet Controller
[HttpGet]
[Route("UserId")]
public async Task<ActionResult<ApplicationUser>> GetUserId(string Username)
{
var user = await userManager.FindByNameAsync(Username);
if (user == null)
return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "Error", Message = "User not exist" });
var result = await userManager.GetUserIdAsync(user);
return new JsonResult(result);
}
Controller return result
"85e39a3e-8101-4166-9193-5e41bec1a7ce"
Function
private async Task Login()
{
var user = new userName { Username = Username };
var loginUser = new LoginDb { Username = Username, Password = Password };
if (Username == null || Password == null)
{
toastService.ShowWarning("Please enter Username and Password");
}
else
{
user = await Http.GetFromJsonAsync<userName>("Authentication/UserId?Username=" + Username);
if (user != null)
{
string Id = System.Text.Json.JsonSerializer.Serialize(user);
var result = await Http.PostAsJsonAsync("Authentication/login", loginUser);
if (result.IsSuccessStatusCode)
{
NavigationManager.NavigateTo("/profile/" + Id);
toastService.ShowSuccess("Login successful");
}
else
{
toastService.ShowError("Username or Password is wrong");
}
}
else
{
NavigationManager.NavigateTo("/login");
}
}
}
OK, I can see a few problems.
On the Server:
[HttpGet]
[Route("UserId")]
public async Task<ActionResult<ApplicationUser>> GetUserId(string Username) // A
{
var user = await userManager.FindByNameAsync(Username);
if (user == null) // B
return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "Error", Message = "User not exist" });
var result = await userManager.GetUserIdAsync(user);
return new JsonResult(result);
}
First, your return type here is Task<ActionResult<ApplicationUser>> . ApplicationUser is tied to the backend Identity library, you can't and shouldn't use it for a DTO.
And you don't, in the end you have return new JsonResult(result); which is OK when you change the return type to just Task<ActionResult>.
On the client:
//user = await Http.GetFromJsonAsync<userName>("Authentication/UserId?Username=" + Username);
var userId = await Http.GetFromJsonAsync<string>("Authentication/UserId?Username=" + Username);
The endpoint returns a simple string. Json does not know about 'UserName' or anything else.
//string Id = System.Text.Json.JsonSerializer.Serialize(user); -- use UserId
You are serializing the Id (again) here, making it almost certainly invalid for an URL. So just skip that.

identity server 4 Logout from all clients using FrontChannelLogoutUri not working

Here I want to achieve the SSO feature. if I logout from Identity server all clients connected to that with that userid should be logged out. But it is working for me.
I am using Identity Server Version="4.0.0"
I have setup the IDS clients , UI templates for loggedout, MVC client as below. But it is not signout from all clients.
new Client
{
ClientId = "testmvc",
AllowedGrantTypes = GrantTypes.Code,
// secret for authentication
ClientSecrets =
{
new Secret("Secret".Sha256())
},
AllowOfflineAccess = true,
// scopes that client has access to
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Address,
"roles"
},
// where to redirect to after login
RedirectUris = { "https://localhost:5002/signin-oidc" },
// where to redirect to after logout
PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" },
FrontChannelLogoutUri = "https://localhost:5002/home/frontchannellogout" ,
}
In MVC client I have created two logouts
//UI Logout
public async Task<IActionResult> Logout()
{
var client = _httpClientFactory.CreateClient("IDPClient");
var discoveryDocumentResponse = await client.GetDiscoveryDocumentAsync();
if (discoveryDocumentResponse.IsError)
{
throw new Exception(discoveryDocumentResponse.Error);
}
var accessTokenRevocationResponse = await client.RevokeTokenAsync(
new TokenRevocationRequest
{
Address = discoveryDocumentResponse.RevocationEndpoint,
ClientId = "testmvc",
ClientSecret = "Secret",
Token = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken)
});
if (accessTokenRevocationResponse.IsError)
{
throw new Exception(accessTokenRevocationResponse.Error);
}
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);
return Redirect(discoveryDocumentResponse.EndSessionEndpoint);
}
//Front channel logout
public async Task<IActionResult> FrontChannelLogout(string sid)
{
if (User.Identity.IsAuthenticated)
{
var currentSid = User.FindFirst("sid")?.Value ?? "";
if (string.Equals(currentSid, sid, StringComparison.Ordinal))
{
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
}
return NoContent();
}
In the Logout method, you are signing out from both auth schemes (Cookies and OIDC), whereas in FrontChannelLogout you seem to be signing out only from the Cookies scheme. Isn't that the problem?
Either way, please try to define the methods like below, and check if the corresponding OIDC logout endpoint is called.
public async Task Logout()
{
// ...
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);
}
public async Task FrontChannelLogout(string sid)
{
if (User.Identity.IsAuthenticated)
{
//...
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);
}
}

User.identity.name is null on ASP.net core api after authentication

I'm totally new to asp.net, and I'm trying to do a simple rest api.
i need to get the username of the current logged in user.
I have User.Identity.IsAuthenticated is true, but User.Identity.Name is null. I'm using jwt tokens for authentication.
here is my login method
[HttpPost]
public async Task<IActionResult> Login([FromBody] LoginData jsonUser) {
IdentityUser claimedUser;
try {
claimedUser = _userManager.Users.First(
user => user.Email == jsonUser.username || user.UserName == jsonUser.username);
} catch (Exception e) {
return NotFound("No user with this username : " + e);
}
bool isPasswordCorrect = await _userManager.CheckPasswordAsync(claimedUser, jsonUser.Password);
return isPasswordCorrect
? Ok(GenerateJwtToken(claimedUser.Email, claimedUser))
: StatusCode(401, "bad login or password");
}
//Génération du token JWT
private string GenerateJwtToken(string email, IdentityUser user) {
var claims = new List<Claim> {
new Claim(JwtRegisteredClaimNames.Sub, email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.NameIdentifier, user.Id),
new Claim(JwtRegisteredClaimNames.UniqueName, user.UserName),
new Claim(JwtRegisteredClaimNames.GivenName, user.UserName)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JwtKey"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var expires = DateTime.Now.AddDays(Convert.ToDouble(_configuration["JwtExpireDays"]));
var token = new JwtSecurityToken(
_configuration["JwtIssuer"],
_configuration["JwtIssuer"],
claims,
expires: expires,
signingCredentials: creds
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
How can I get the current username ?

Response for preflight is invalid (redirect) using Asp.Net Web API

I'm developing a website which uses AngularJS in the frontend and ASP.NET web API in the backend. I'm trying to configure external logins(Facebook) for my site. I have already enabled Cors on the API. The order of API calls are:
API call to
api/Account/ExternalLogins?returnUrl=%2F&generateState=true
to get a list of external login providers.
This returns
[{"name":"Facebook",
"url":"/api/Account/ExternalLogin?provider=Facebook&response_type=token&client_id=self&redirect_uri=http%3A%2F%2Flocalhost%3A15359%2F&state=xxxxxxxxxx",
"state":"xxxxxxxxxx"}]
Now I send a GET request to url returned earlier. This triggers a preflight request to https://www.facebook.com/dialog/oauth?response_type=code&client_id=xxxxxxx&redirect_uri=https%3A%2F%2Flocalhost%3A44327%2Fsignin-facebook&scope=manage_pages&state=xxxxxxx which gives error. This is never redirected to Facebook Login Page.
Both the request and response headers for /api/Account/ExternalLogin?provider=Facebook&response_type=token&client_id=self&redirect_uri=http%3A%2F%2Flocalhost%3A15359%2F&state=xxxxxxxxxx contains Access-Control-Allow-Origin:*
But for the preflight request https://www.facebook.com/dialog/oauth?response_type=code&client_id=xxxxxxx&redirect_uri=https%3A%2F%2Flocalhost%3A44327%2Fsignin-facebook&scope=manage_pages&state=xxxxxxx Access-Control-Allow-Origin header is missing.
The code snippets are mentioned below.
app.js
$httpProvider.defaults.headers.common['Access-Control-Allow-Origin'] = '*';
$httpProvider.defaults.headers.common['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, Content-Length, X-Requested-With';
$httpProvider.defaults.headers.common['Access-Control-Allow-Credentials'] = true;
$httpProvider.defaults.headers.common['Access-Control-Allow-Method'] = 'GET, PUT, POST, DELETE, OPTIONS';
Start.Auth.cs
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.UseCors(CorsOptions.AllowAll);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/api/Account/ExternalLogin"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// 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
//Provider = new AuthorizationServerProvider()
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
// Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
// Enables the application to remember the second login verification factor such as phone or email.
// Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
// This is similar to the RememberMe option when you log in.
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions
{
AppId = "xxxxxxxxx",
AppSecret = "xxxxxxxxx",
BackchannelHttpHandler = new FacebookBackChannelHandler(),
Provider = new FacebookAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("FacebookAccessToken", context.AccessToken));
return Task.FromResult(0);
}
},
SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie,
SendAppSecretProof = true
};
facebookOptions.Scope.Add(ConfigurationManager.AppSettings["Facebook_Scope"]);
facebookOptions.AuthenticationMode = AuthenticationMode.Passive;
app.UseFacebookAuthentication(facebookOptions);
AccountController
[Authorize]
[EnableCors(origins: "*", headers: "*", methods: "*")]
[RoutePrefix("api/Account")]
public class AccountController : ApiController
{
private const string LocalLoginProvider = "Local";
private ApplicationUserManager _userManager;
public AccountController()
{
}
public AccountController(ApplicationUserManager userManager,
ISecureDataFormat<AuthenticationTicket> accessTokenFormat)
{
UserManager = userManager;
AccessTokenFormat = accessTokenFormat;
}
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? Request.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
public ISecureDataFormat<AuthenticationTicket> AccessTokenFormat { get; private set; }
// GET api/Account/UserInfo
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("UserInfo")]
public UserInfoViewModel GetUserInfo()
{
ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);
return new UserInfoViewModel
{
Email = User.Identity.GetUserName(),
HasRegistered = externalLogin == null,
LoginProvider = externalLogin != null ? externalLogin.LoginProvider : null
};
}
// POST api/Account/Logout
[Route("Logout")]
[AllowAnonymous]
public IHttpActionResult Logout()
{
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
return Ok();
}
// POST api/Account/AddExternalLogin
[Route("AddExternalLogin")]
public async Task<IHttpActionResult> AddExternalLogin(AddExternalLoginBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
AuthenticationTicket ticket = AccessTokenFormat.Unprotect(model.ExternalAccessToken);
if (ticket == null || ticket.Identity == null || (ticket.Properties != null
&& ticket.Properties.ExpiresUtc.HasValue
&& ticket.Properties.ExpiresUtc.Value < DateTimeOffset.UtcNow))
{
return BadRequest("External login failure.");
}
ExternalLoginData externalData = ExternalLoginData.FromIdentity(ticket.Identity);
if (externalData == null)
{
return BadRequest("The external login is already associated with an account.");
}
IdentityResult result = await UserManager.AddLoginAsync(User.Identity.GetUserId(),
new UserLoginInfo(externalData.LoginProvider, externalData.ProviderKey));
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
// GET api/Account/ExternalLogin
[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
{
IEnumerable<Claim> claims = externalLogin.GetClaims();
ClaimsIdentity identity = new ClaimsIdentity(claims, OAuthDefaults.AuthenticationType);
Authentication.SignIn(identity);
}
return Ok();
}
// GET api/Account/ExternalLogins?returnUrl=%2F&generateState=true
[AllowAnonymous]
[Route("ExternalLogins")]
public IEnumerable<ExternalLoginViewModel> GetExternalLogins(string returnUrl, bool generateState = false)
{
IEnumerable<AuthenticationDescription> descriptions = Authentication.GetExternalAuthenticationTypes();
List<ExternalLoginViewModel> logins = new List<ExternalLoginViewModel>();
string state;
if (generateState)
{
const int strengthInBits = 256;
state = RandomOAuthStateGenerator.Generate(strengthInBits);
}
else
{
state = null;
}
foreach (AuthenticationDescription description in descriptions)
{
ExternalLoginViewModel login = new ExternalLoginViewModel
{
Name = description.Caption,
Url = Url.Route("ExternalLogin", new
{
provider = description.AuthenticationType,
response_type = "token",
client_id = Startup.PublicClientId,
redirect_uri = new Uri(Request.RequestUri, returnUrl).AbsoluteUri,
state = state
}),
State = state
};
logins.Add(login);
}
return logins;
}
// POST api/Account/Register
[AllowAnonymous]
[Route("Register")]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
// POST api/Account/RegisterExternal
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("RegisterExternal")]
public async Task<IHttpActionResult> RegisterExternal(RegisterExternalBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var info = await Authentication.GetExternalLoginInfoAsync();
if (info == null)
{
return InternalServerError();
}
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
result = await UserManager.AddLoginAsync(user.Id, info.Login);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
protected override void Dispose(bool disposing)
{
if (disposing && _userManager != null)
{
_userManager.Dispose();
_userManager = null;
}
base.Dispose(disposing);
}
}
I know this is achievable using MVC Account Controller. I'm looking for a way to do this entirely using web api with no dependency on MVC.
Thanks!

Adding Scope, "wl.emails", to MicrosoftAccountAuthenticationOptions in Startup.Auth.cs causes issues

Please tell me what is wrong.
public void ConfigureAuth(IAppBuilder app)
{
var mo = new MicrosoftAccountAuthenticationOptions();
mo.ClientId = "xxxxxxxxxxxxxxxxx";
mo.ClientSecret = "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy";
mo.Scope.Add("wl.basic"); // No effect if this commented out
mo.Scope.Add("wl.emails");
// IF I COMMENT NEXT TWO PROPERTIES, USER IS AUTHENTICATED, BUT THE DB IS NOT
// UPDATED. LEAVE THEM AND THE REDIRECT FROM MSLIVE ENDS ON LOGIN PAGE
mo.SignInAsAuthenticationType = "External";
mo.Provider = new MicrosoftAccountAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
// Set breakpoint here to see the context.Identity.Claims HAS CLAIMS DESIRED.
// SO IT APPEARS TO ME Nothing to do here but verify they exist in the debugger.
//(context.Identity.Claims).Items ARE:
//{http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier: xxxxxxxxx}
//{http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name: yyyy yyyyy}
//{urn:microsoftaccount:id: xxxxxxxx}
//{urn:microsoftaccount:name: yyyy yyyyy}
//{http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress: xxxxxxxx#hotmail.com}
return Task.FromResult(0);
}
};
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseMicrosoftAccountAuthentication(mo);
}
A reasonable expectation asserts that the framwework will transparently handle the addition of a Scope to the default *AuthenticationOptions. Subsequently, wrt the MVC5 template, the developer can extract and persist Claims in ExternalLoginConfirmation code. Another reasonable expectation is that the framework will transform incoming standard ClaimTypes into Claims in the ClaimsIdentity exposed by the framework.
I am very glad source code is available MicrosoftAccountAutheticationHandler.cs, and I will check it to solve this; lacking a response. Best wishes to Katana as the documentation and the framework are maturing. Is there a way for the framework to help the developer spot config issues?.
I would have agreed with you if we both had not hit the same logical brick wall of reasonning ....
I think it is something to do with a detached Owin Security Context while the web application operates in a seperate Context and you have to 'seed' the web one. So what I've deduced is this:
in Startup.Auth.cs
var microsoftOptions =
new Microsoft.Owin.Security.MicrosoftAccount.MicrosoftAccountAuthenticationOptions
{
CallbackPath = new Microsoft.Owin.PathString("/Callbacks/External"),//register at oAuth provider
ClientId = "xxxx",
ClientSecret = "yyyyyyyyyyyyyyyyy",
Provider = new Microsoft.Owin.Security.MicrosoftAccount.MicrosoftAccountAuthenticationProvider
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new Claim(providerKey, context.Identity.AuthenticationType));
context.Identity.AddClaim(new Claim(ClaimTypes.Name, context.Identity.FindFirstValue(ClaimTypes.Name)));
return System.Threading.Tasks.Task.FromResult(0);
}
}
};
microsoftOptions.Scope.Add("wl.basic");
microsoftOptions.Scope.Add("wl.emails");
app.UseMicrosoftAccountAuthentication(microsoftOptions);
and in AccountController:
[AllowAnonymous]
public async Task<ActionResult> oAuthCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
if (User.Identity.IsAuthenticated)
return RedirectToAction("Index", "Manage");
else
return RedirectToAction("Login");
}
var currentUser = await UserManager.FindAsync(loginInfo.Login);
if (currentUser != null)
{
await StoreExternalTokensOnLocalContext(currentUser);
}
//.... rest as same as per AspNet Sample project.
}
private async Task StoreExternalTokensOnLocalContext(ApplicationUser user)
{
if (user == null)
return;
var externalIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
if (externalIdentity != null)
{
// Retrieve the existing claims for the user and add the FacebookAccessTokenClaim
var currentClaims = await UserManager.GetClaimsAsync(user.Id);
//var providerClaim = externalIdentity.FindFirstValue("provider") ?? string.Empty;
await StoreClaim("provider", user.Id, externalIdentity);
await StoreClaim("FacebookId", user.Id, externalIdentity);
await StoreClaim("image", user.Id, externalIdentity);
await StoreClaim("link", user.Id, externalIdentity);
await StoreClaim(ClaimTypes.Name, user.Id, externalIdentity);
await StoreClaim(ClaimTypes.Email, user.Id, externalIdentity);
var addedClaims = await UserManager.GetClaimsAsync(user.Id);
}
}
private async Task StoreClaim(string typeName, string userId, ClaimsIdentity externalIdentity)
{
var providerClaim = externalIdentity.Claims.FirstOrDefault(c => c.Type.Equals(typeName));
if (providerClaim == null)
return;
var previousClaims = await UserManager.GetClaimsAsync(userId);
if (previousClaims.IndexOf(providerClaim) >= 0)
return;
var idResult = await UserManager.AddClaimAsync(userId, providerClaim);
}

Resources