Role based tokens ASP.net Identity - asp.net

I am using the standard ASP.net OWIN OAuth middleware system to authenticate local users with Bearer tokens. What I would like to do is is hand out role-based tokens for the same user account. eg.
OAuth TokenA => General User Privileges
UserA ->
OAuth TokenB => Admin User Privileges
Is this supported in any way?

I was able to solve this using the following method -
//ensure the token is a User role token only
identity.AddClaim(new Claim(ClaimTypes.Role, "User"));
Where 'identity' is an instance of
System.Security.Claims.Identity
Then in my System.Web.Http.AuthorizeAttribute implementation, I can check the claim like so-
//get claims of the Role type
var identity = (ClaimsIdentity)actionContext.RequestContext.Principal.Identity;
IEnumerable<Claim> claims = identity.Claims.Where(c => c.Type == ClaimTypes.Role);
//check if any claim for the User role, if so this is a non-privleged token
var nonPrivToken = claims.Any(c => c.Value == "User");

You can add claims to the user just before the bearer token is generated. So if you change the things you put into, two different bearer token can be generated and consumed.
(From the taiseer-joudeh-blog)
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
using (AuthRepository _repo = new AuthRepository())
{
IdentityUser user = await _repo.FindUser(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
// Change the role and create new bearer token
identity.AddClaim(new Claim("role", "user"));
context.Validated(identity);
}
}

Related

How to store cookies in asp.net identity (web api)

I have Asp.net web Application project with WebApi and individuals user Account.
I implemented Registration and login, and I used angularjs in front end.
Now, I need to store cookies in browser.
I'm confuse if authentication based on cookies store cookies in browser?
In my project I authenticate users based on token.
I do research but it confuse me and I did't find a clear guide to store cookies using Asp.Net Identity webApi.
Here is where is user authenticate:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
/*
I tried the following but I get an error that Request doesn't exist
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.Name, context.UserName));
var id = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
var ctx = Request.GetOwinContext();
var authenticationManager = ctx.Authentication;
authenticationManager.SignIn(id);
*/
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
I'm sorry, I'm new in Asp.net Identity.
If there is a clear guide to do this in Asp.Net Identity in WebApi (Not MVC)?
Note: I don't have LoginPath.
So you have a webApi that you use to authenticate your users and when your user is authenticated you get a jwt token, Right ? and you need to save that token in a cookie so that your front-end would use to make requests.
so here is a sample code i wrote to do just that is asp core api
in my aspcore API AccountsController => Login Action:
[HttpPost]
public async Task<IActionResult> Login([FromBody] LoginViewModel vm)
{
if (ModelState.IsValid)
{
var token = await _authenticationService.GenerateToken(vm);
if (token != null)
{
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo
});
}
return Unauthorized();
}
return StatusCode(StatusCodes.Status403Forbidden, new { ErrorMessage = "wrong Email or password" });
}
the _authenticationService is a class that i wrote to generatae jwt token :
public async Task<JwtSecurityToken> GenerateToken(LoginViewModel vm)
{
if (!string.IsNullOrEmpty(vm.Email) && !string.IsNullOrEmpty(vm.Password))
{
var user = await _userManager.FindByEmailAsync(vm.Email);
var userRoles = await _userManager.GetRolesAsync(user);
var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.GetSection("Secrets:SigningKey").Value));
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, vm.Email)
};
foreach (var roleName in userRoles)
{
claims.Add(new Claim(ClaimsIdentity.DefaultRoleClaimType, roleName));
};
if (user != null && await _userManager.CheckPasswordAsync(user, vm.Password))
{
var token = new JwtSecurityToken(
issuer: _configuration.GetSection("Secrets:issuer").Value,
audience: _configuration.GetSection("Secrets:audience").Value,
expires: DateTime.UtcNow.AddDays(20),
signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256),
claims: claims);
return token;
}
return null;
}
return null;
}
so in the code above you if you send a good credentials to your api you will get a jwt token back.
now you need to save that token in a cookie, to do that you need to create the cookie in your front-end not your web api.
so you might see this link in angular-university

ASP.NET Core Jwt implement signinmanager claims

I have implemented Jwt as a way to authenticate my user. However, I am stuck on how I can do certain things on my application with regards to roles. Currently my Jwt Token contains the users email, phone , id and a list of roles that they have.
What I do with that token is like this:
[TypeFilter(typeof(ValidateRolesFilter), Arguments = new object[] {
ApplicationGlobals.ApplicationSecretKey, RoleGlobals.SystemAdministrator
})]
public IActionResult Index()
{
return View();
}
My Typefilter contains a rest request that sends the token to another application to verify if my user can access that Function. However,
I am stuck when it comes to the view. I want to segment certain containers to be allowed to be viewed by certain users with certain roles.
I have an idea that if I were to add my users claims to the signinmanager just like a non jwt application, i would be able to get the claims from the httpcontext. However, I don't know if what I have can work with an application that uses jwt.
public async Task SignInUserAsync(TIdentityUser user, bool isPersistent, IEnumerable<Claim> customClaims)
{
var claimsPrincipal = await _signInManager.CreateUserPrincipalAsync(user);
var identity = claimsPrincipal.Identity as ClaimsIdentity;
var claims = (from c in claimsPrincipal.Claims select c).ToList();
var savedClaims = claims;
foreach (var item in claims)
{
identity.RemoveClaim(item);
}
if (customClaims != null)
{
identity.AddClaim(savedClaims[0]);
identity.AddClaim(savedClaims[1]);
identity.AddClaim(savedClaims[2]);
identity.AddClaims(customClaims);
}
await _signInManager.Context.SignInAsync(IdentityConstants.ApplicationScheme,
claimsPrincipal,
new AuthenticationProperties { IsPersistent = isPersistent });
}
I am recently doing a cooperative project on JWT. I wrote a middlware, when ever the user request to the api, It is checked by the Authentication middleware. I read the userRole from db and put it in the identity priciple I am sharing the middleware codes.
In here I read the JWT middle part to extract the user information
public class AuthenticationMiddleware
{
private readonly RequestDelegate _next;
// Dependency Injection
public AuthenticationMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
string authHeader = context.Request.Headers["Authorization"];
if (authHeader != null)
{
int startPoint = authHeader.IndexOf(".") + 1;
int endPoint = authHeader.LastIndexOf(".");
var tokenString = authHeader.Substring(startPoint, endPoint - startPoint).Split(".");
var token = tokenString[0].ToString()+"==";
var credentialString = Encoding.UTF8
.GetString(Convert.FromBase64String(token));
// Splitting the data from Jwt
var credentials = credentialString.Split(new char[] { ':',',' });
// Trim this string.
var userRule = credentials[5].Replace("\"", "");
var userName = credentials[3].Replace("\"", "");
// Identity Principal
var claims = new[]
{
new Claim("name", userName),
new Claim(ClaimTypes.Role, userRule),
};
var identity = new ClaimsIdentity(claims, "basic");
context.User = new ClaimsPrincipal(identity);
}
await _next(context);
}
}
In startup.cs you need to call this middleware in the configure method
app.UseMiddleware<AuthenticationMiddleware>();
In the controller
[HttpGet("GetUsers")]
[Authorize(Roles = "admin")]
public ActionResult GetUsers()
{
var users = _authRepository.GetUsers();
return Ok(users);
}
if You need any help please give a comment. This implementation really worked for me. Check my repositories on the subject: https://github.com/hidayatarg/Asp.net-Core-2.1-Jwt-Authentication-Middleware
https://github.com/hidayatarg/Decode-JWT-Token
JSON Web Tokens consist of three parts separated by dots (.), which are: Header,Payload,Signature .Therefore, a JWT typically looks like xxxxx.yyyyy.zzzzz .The second part of the token is the payload, which contains the claims.
You can decode the access token to get the claims which related to your roles :
How to decode JWT Token? .
Decoding and verifying JWT token using System.IdentityModel.Tokens.Jwt
If you are using Owin OpenID Connect middlerware to autheticate user from identity provider like Azure AD , Idenity server 4.... You can add additional claims to principal under OnTokenValidated event .
Edit :
You can also add the claims(decode and get the claims) to user context before sign- in :
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme, ClaimTypes.Name, ClaimTypes.Role);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, loginData.Username));
identity.AddClaim(new Claim(ClaimTypes.Name, loginData.Username));
//add your custom claims
....
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, new AuthenticationProperties { IsPersistent = loginData.RememberMe });
Reference : http://future-shock.net/blog/post/creating-a-simple-login-in-asp.net-core-2-using-authentication-and-authorization-not-identity
Then you can access the claims in view like :
#foreach (var item in Context.User.Claims)
{
<p>#item.Value</p>
};

ASP.NET Core Identity JWT always unauthorized

I'm following FullStack Mark's guide to setup an aspnet core with identity and angular, the problem is that i'm not getting the authorization part to work, i always get an 401 unauthorized, registering is working, and logging in is working too, i think that the problem may be in the generated token (it says invalid signature in jwt.io validation)
sorry if the question is poorly formulated, first time asking.
FullStack Mark's guide that i'm following: https://fullstackmark.com/post/13/jwt-authentication-with-aspnet-core-2-web-api-angular-5-net-core-identity-and-facebook-login
my entire application code : https://github.com/GiovaniHarada/AngularConce/tree/master/Conce
Login Controller
[HttpPost("login")]
public async Task<IActionResult> Post([FromBody]CredentialsResource credentials)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var identity = await GetClaimsIdentity(credentials.UserName, credentials.Password);
if (identity == null)
{
return BadRequest(Errors.AddErrorToModelState("login_failure", "Invalid username or password.", ModelState));
}
var jwt = await Tokens.GenerateJwt(identity, _jwtFactory, credentials.UserName, _jwtOptions, new JsonSerializerSettings { Formatting = Formatting.Indented });
return new OkObjectResult(jwt);
}
Get Claims Identity
private async Task<ClaimsIdentity> GetClaimsIdentity(string userName, string password)
{
if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password))
return await Task.FromResult<ClaimsIdentity>(null);
// get the user to verifty
var userToVerify = await _userManager.FindByNameAsync(userName);
if (userToVerify == null) return await Task.FromResult<ClaimsIdentity>(null);
// check the credentials
if (await _userManager.CheckPasswordAsync(userToVerify, password))
{
return await Task.FromResult(_jwtFactory.GenerateClaimsIdentity(userName, userToVerify.Id));
}
// Credentials are invalid, or account doesn't exist
return await Task.FromResult<ClaimsIdentity>(null);
}
Generate Encoded Token function
public async Task<string> GenerateEncodedToken(string userName, ClaimsIdentity identity)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, userName),
new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()),
new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(), ClaimValueTypes.Integer64),
identity.FindFirst(Helpers.Constants.Strings.JwtClaimIdentifiers.Rol),
identity.FindFirst(Helpers.Constants.Strings.JwtClaimIdentifiers.Id)
};
// Create the JWT security token and encode it.
var jwt = new JwtSecurityToken(
issuer: _jwtOptions.Issuer,
audience: _jwtOptions.Audience,
claims: claims,
notBefore: _jwtOptions.NotBefore,
expires: _jwtOptions.Expiration,
signingCredentials: _jwtOptions.SigningCredentials);
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
return encodedJwt;
}

ASP.NET OAuth Authorization - Difference between using ClientId and Secret and Username and Password

I'm trying to implement a simple OAuthAuthorizationServerProvider in ASP.NET WebAPI 2. My main purpose is to learn how to have a token for a mobile app. I would like users to login with username & password, and then receive a token (and a refresh token so they won't have to re-enter credentials once token expires). Later on, I would like to have the chance to open the API for external use by other applications (like one uses Facebook api and such...).
Here is how I've set-up my AuthorizationServer:
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(5),
Provider = new SimpleAuthorizationServerProvider(new SimpleAuthorizationServerProviderOptions()
{
ValidateUserCredentialsFunction = ValidateUser
}),
RefreshTokenProvider = new SimpleRefreshTokenProvider()
});
This is my SimpleAuthorizationServerProviderOptions implementation:
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public delegate Task<bool> ClientCredentialsValidationFunction(string clientid, string secret);
public delegate Task<IEnumerable<Claim>> UserCredentialValidationFunction(string username, string password);
public SimpleAuthorizationServerProviderOptions Options { get; private set; }
public SimpleAuthorizationServerProvider(SimpleAuthorizationServerProviderOptions options)
{
if (options.ValidateUserCredentialsFunction == null)
{
throw new NullReferenceException("ValidateUserCredentialsFunction cannot be null");
}
Options = options;
}
public SimpleAuthorizationServerProvider(UserCredentialValidationFunction userCredentialValidationFunction)
{
Options = new SimpleAuthorizationServerProviderOptions()
{
ValidateUserCredentialsFunction = userCredentialValidationFunction
};
}
public SimpleAuthorizationServerProvider(UserCredentialValidationFunction userCredentialValidationFunction, ClientCredentialsValidationFunction clientCredentialsValidationFunction)
{
Options = new SimpleAuthorizationServerProviderOptions()
{
ValidateUserCredentialsFunction = userCredentialValidationFunction,
ValidateClientCredentialsFunction = clientCredentialsValidationFunction
};
}
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
if (Options.ValidateClientCredentialsFunction != null)
{
string clientId, clientSecret;
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
{
context.TryGetFormCredentials(out clientId, out clientSecret);
}
var clientValidated = await Options.ValidateClientCredentialsFunction(clientId, clientSecret);
if (!clientValidated)
{
context.Rejected();
return;
}
}
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
if (Options.ValidateUserCredentialsFunction == null)
{
throw new NullReferenceException("ValidateUserCredentialsFunction cannot be null");
}
var claims = await Options.ValidateUserCredentialsFunction(context.UserName, context.Password);
if (claims == null)
{
context.Rejected();
return;
}
// create identity
var identity = new ClaimsIdentity(claims, context.Options.AuthenticationType);
// create metadata to pass to refresh token provider
var props = new AuthenticationProperties(new Dictionary<string, string>()
{
{ "as:client_id", context.UserName }
});
var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);
}
public override async Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
var originalClient = context.Ticket.Properties.Dictionary["as:client_id"];
var currentClient = context.ClientId;
// enforce client binding of refresh token
if (originalClient != currentClient)
{
context.Rejected();
return;
}
// chance to change authentication ticket for refresh token requests
var newIdentity = new ClaimsIdentity(context.Ticket.Identity);
newIdentity.AddClaim(new Claim("newClaim", "refreshToken"));
var newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties);
context.Validated(newTicket);
}
}
And my SimpleRefreshTokenProvider implementation:
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
{
private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens =
new ConcurrentDictionary<string, AuthenticationTicket>();
public void Create(AuthenticationTokenCreateContext context)
{
}
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var guid = Guid.NewGuid().ToString();
var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary)
{
IssuedUtc = context.Ticket.Properties.IssuedUtc,
ExpiresUtc = DateTime.UtcNow.AddYears(1)
};
var refreshTokenTicket = new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties);
_refreshTokens.TryAdd(guid, refreshTokenTicket);
context.SetToken(guid);
}
public void Receive(AuthenticationTokenReceiveContext context)
{
}
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
AuthenticationTicket ticket;
if (_refreshTokens.TryRemove(context.Token, out ticket))
{
context.SetTicket(ticket);
}
}
}
What I don't fully understand is the use of ClientId and Secret vs Username and Password. The code I pasted generates a token by username and password and I can work with that token (until it expires), but when I try to get a refresh token, I must have the ClientId.
Also, if a token expires, the correct way is to send the refresh token and get a new token? What if the refresh token gets stolen? isn't it the same as a username & password getting stolen?
What I don't fully understand is the use of ClientId and Secret vs Username and Password. The code I pasted generates a token by username and password and I can work with that token (until it expires), but when I try to get a refresh token, I must have the ClientId.
Also, if a token expires, the correct way is to send the refresh token and get a new token? What if the refresh token gets stolen? isn't it the same as a username & password getting stolen?
In OAuth2 is essential to authenticate both the user and the client in any authorization flow defined by the protocol. The client authentication (as you may guess) enforces the use of your API only by known clients. The serialized access token, once generated, is not bound to a specific client directly. Please note that the ClientSecret must be treated as a confidential information, and can be used only by clients that can store this information in some secure way (e.g. external services clients, but not javascript clients).
The refresh token is simply an alternative "grant type" for OAuth2, and, as you stated correctly, will substitute the username and password pair for a User. This token must be treated as confidential data (even more confidential than the access token), but gives advantages over storing the username & password on the client:
it can be revoked by the user if compromised;
it has a limited lifetime (usually days or weeks);
it does not expose user credentials (an attacker can only get access tokens for the "scope" the refresh token was issued).
I suggest you to read more about the different grant types defined in OAuth 2 checking in the official draft. I also recommend you this resource I found very useful when firstly implemented OAuth2 in Web API myself.
Sample requests
Here are two request examples using fiddler, for Resource Owner Password Credentials Grant:
and for Refresh Token Grant:

MVC 5 Web API with Facebook access token to RegisterExternal without need of Cookie

Setup:
New MVC5 Project with just Web API. Added Facebook AppId and Secret.
I can get Token for my Web API from Token endpoint by passing in UserName and Password. Then use that token for further calls.
BUT
I want to register new users with the help of Facebook SDK in iOS app.
I am using Facebook SDK to get Access Token. (Assume at this point, I have an Access Token).
Next thing I know is to call api/Account/RegisterExternal endpoint by passing this token in Authorization header with Bearer [Access Token] but this result in 500 server error.
I guess I know the reason, Cookie is missing. I made the same call with a cookie from Fidler and it worked. (Cookie is received by going to URL provided by ExternalLogins endpoint).
As cookie is missing await Authentication.GetExternalLoginInfoAsync(); inside the RegisterExternal action returns null.
// 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();
}
I don't want to make 3 calls to my Web API to ask for external logins and then goto that URL and authenticate in a Web Browser for Facebook access token and then call the RegisterExternal endpoint with that access token and Cookie that I need to collect between these calls.
As I said I didn't change anything in template except the Facebook Ids. Still the code is as below.
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static string PublicClientId { get; private set; }
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// 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
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),
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
app.UseFacebookAuthentication(
appId: "xxxxxxxxxxxxxxx",
appSecret: "xxxxxxxxxxxxxxxxxxxxxxxx");
}
}
as far as I know, Web API doesn't need Cookie and that appears true when I have Local Token from Token endpoint but why does it require Cookie in the first place when doing ExternalRegister
WebApiConfig class looks like this and shouldn't config.SuppressDefaultHostAuthentication(); avoid any Cookie needs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
I don't know if I am missing the point here.. My intentions are to not need to use web browser in a native iOS app for the token. That is Facebook SDK to get access token and using that call RegisterExternal to get the Local Token and create that users Identity.
I did my homework and I am stuck on this thought.
Thoughts appreciated!
I was mistaken that it accepts the Social Token with cookie!
It doesn't accept any External Token directly.
The thing is.. MVC 5 is taking care of everything for us, i.e. collecting token from Social Medias and validating/processing it. After that it generates a local token.
The RegisterExternal method also requires cookies to be maintained, the solution does not.
I have written a blog post which will explain in detail. Added the straight forward answer below. I aimed to make it blend and feel integral part of Login/Signup flow of default MVC Web API to make sure its easy to understand.
After the below solution, Authorize attribute must be as below to work or you will get Unauthorized response.
[Authorize]
[HostAuthentication(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ExternalBearer)]
[HostAuthentication(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ApplicationCookie)]
Use ExternalBearer if you want to allow only Tokens to use API, use ApplicationCookie if you want to allow only Logged cookie to use API i.e. from a website. User both if you want to allow the API for both.
Add this action to AccountController.cs
// POST api/Account/RegisterExternalToken
[OverrideAuthentication]
[AllowAnonymous]
[Route("RegisterExternalToken")]
public async Task<IHttpActionResult> RegisterExternalToken(RegisterExternalTokenBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
ExternalLoginData externalLogin = await ExternalLoginData.FromToken(model.Provider, model.Token);
if (externalLogin == null)
{
return InternalServerError();
}
if (externalLogin.LoginProvider != model.Provider)
{
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
return InternalServerError();
}
ApplicationUser user = await UserManager.FindAsync(new UserLoginInfo(externalLogin.LoginProvider,
externalLogin.ProviderKey));
bool hasRegistered = user != null;
ClaimsIdentity identity = null;
IdentityResult result;
if (hasRegistered)
{
identity = await UserManager.CreateIdentityAsync(user, OAuthDefaults.AuthenticationType);
IEnumerable<Claim> claims = externalLogin.GetClaims();
identity.AddClaims(claims);
Authentication.SignIn(identity);
}
else
{
user = new ApplicationUser() { Id = Guid.NewGuid().ToString(), UserName = model.Email, Email = model.Email };
result = await UserManager.CreateAsync(user);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
var info = new ExternalLoginInfo()
{
DefaultUserName = model.Email,
Login = new UserLoginInfo(model.Provider, externalLogin.ProviderKey)
};
result = await UserManager.AddLoginAsync(user.Id, info.Login);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
identity = await UserManager.CreateIdentityAsync(user, OAuthDefaults.AuthenticationType);
IEnumerable<Claim> claims = externalLogin.GetClaims();
identity.AddClaims(claims);
Authentication.SignIn(identity);
}
AuthenticationTicket ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
var currentUtc = new Microsoft.Owin.Infrastructure.SystemClock().UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromDays(365));
var accessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
Request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
// Create the response building a JSON object that mimics exactly the one issued by the default /Token endpoint
JObject token = new JObject(
new JProperty("userName", user.UserName),
new JProperty("id", user.Id),
new JProperty("access_token", accessToken),
new JProperty("token_type", "bearer"),
new JProperty("expires_in", TimeSpan.FromDays(365).TotalSeconds.ToString()),
new JProperty(".issued", currentUtc.ToString("ddd, dd MMM yyyy HH':'mm':'ss 'GMT'")),
new JProperty(".expires", currentUtc.Add(TimeSpan.FromDays(365)).ToString("ddd, dd MMM yyyy HH:mm:ss 'GMT'"))
);
return Ok(token);
}
Add this helper method to ExternalLoginData class in helper region in AccountController.cs
public static async Task<ExternalLoginData> FromToken(string provider, string accessToken)
{
string verifyTokenEndPoint = "", verifyAppEndpoint = "";
if (provider == "Facebook")
{
verifyTokenEndPoint = string.Format("https://graph.facebook.com/me?access_token={0}", accessToken);
verifyAppEndpoint = string.Format("https://graph.facebook.com/app?access_token={0}", accessToken);
}
else if (provider == "Google")
{
return null; // not implemented yet
//verifyTokenEndPoint = string.Format("https://www.googleapis.com/oauth2/v1/tokeninfo?access_token={0}", accessToken);
}
else
{
return null;
}
HttpClient client = new HttpClient();
Uri uri = new Uri(verifyTokenEndPoint);
HttpResponseMessage response = await client.GetAsync(uri);
ClaimsIdentity identity = null;
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
dynamic iObj = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(content);
uri = new Uri(verifyAppEndpoint);
response = await client.GetAsync(uri);
content = await response.Content.ReadAsStringAsync();
dynamic appObj = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(content);
identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);
if (provider == "Facebook")
{
if (appObj["id"] != Startup.facebookAuthOptions.AppId)
{
return null;
}
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, iObj["id"].ToString(), ClaimValueTypes.String, "Facebook", "Facebook"));
}
else if (provider == "Google")
{
//not implemented yet
}
}
if (identity == null)
return null;
Claim providerKeyClaim = identity.FindFirst(ClaimTypes.NameIdentifier);
if (providerKeyClaim == null || String.IsNullOrEmpty(providerKeyClaim.Issuer) || String.IsNullOrEmpty(providerKeyClaim.Value))
return null;
if (providerKeyClaim.Issuer == ClaimsIdentity.DefaultIssuer)
return null;
return new ExternalLoginData
{
LoginProvider = providerKeyClaim.Issuer,
ProviderKey = providerKeyClaim.Value,
UserName = identity.FindFirstValue(ClaimTypes.Name)
};
}
and finally, the RegisterExternalTokenBindingModel being used by the action.
public class RegisterExternalTokenBindingModel
{
[Required]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[Display(Name = "Token")]
public string Token { get; set; }
[Required]
[Display(Name = "Provider")]
public string Provider { get; set; }
}
Yes, we pass the email along with Token details while registering, this will not cause you to change the code when using Twitter, as Twitter doesn't provide users email. We verify token comes from our app. Once email registered, hacked or somebody else's token cannot be used to change email or get a local token for that email as it will always return the local token for the actual user of the Social Token passed regardless of the email sent.
RegisterExternalToken endpoint works to get token in both ways i.e. register the user and send the Local token or if the user already registered then send the token.
Before everything, this is NOT A FULL Answer, this is just a note or an addition for the answer to avoid some problems which could cost you handful of days (in my case 3 days)
The previous answer is the full answer it just lacks from one thing, which is the following:
if you specified a role for the Authorize attribute, for example [Authorize("UserRole")] , the previous setup will still give you 401 error because the solution does not set the RoleClaim
and to solve this problem you have to add this line of code to the RegisterExternalToken method
oAuthIdentity.AddClaim(new Claim(ClaimTypes.Role, "UserRole"));

Resources