Is there a way to take a Bearer Token string and convert it to the Identity object manually in asp.net?
Cheers,
Aziz
This is a pretty old question, but I think answer was still missing. I was able to regenerate Principal by using the following line
var ticket = Startup.OAuthOptions.AccessTokenFormat.Unprotect(accessToken);
var identity = ticket.Identity;
First you need to crate some claims based on token then create ClaimsIdentity and use it to authorize the user.
public ActionResoult Login(string token)
{
if(_tokenManager.IsValid(token))
{
// optionally you have own user manager which returns roles and user name from token
// no matter how you store users and roles
var user=_myUserManager.GetUserRoles(token);
// user is valid, going to authenticate user for my App
var ident = new ClaimsIdentity(
new[]
{
// adding following 2 claim just for supporting default antiforgery provider
new Claim(ClaimTypes.NameIdentifier, token),
new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "ASP.NET Identity", "http://www.w3.org/2001/XMLSchema#string"),
// an optional claim you could omit this
new Claim(ClaimTypes.Name, user.Username),
// populate assigned user's role form your DB
// and add each one as a claim
new Claim(ClaimTypes.Role, user.Roles[0]),
new Claim(ClaimTypes.Role, user.Roles[1]),
// and so on
},
DefaultAuthenticationTypes.ApplicationCookie);
// Identity is sign in user based on claim don't matter
// how you generated it
HttpContext.GetOwinContext().Authentication.SignIn(
new AuthenticationProperties { IsPersistent = false }, ident);
// auth is succeed, just from a token
return RedirectToAction("MyAction");
}
// invalid user
ModelState.AddModelError("", "We could not authorize you :(");
return View();
}
Now you could use Authorize filter as well:
[Authorize]
public ActionResult Foo()
{
}
// since we injected user roles to Identity we could do this as well
[Authorize(Roles="admin")]
public ActionResult Foo()
{
// since we injected our authentication mechanism to Identity pipeline
// we have access current user principal by calling also
// HttpContext.User
}
Also I encourage you to have look Token Based Authentication Sample from my github repo as a very simple working example.
The token just holds claims and it's just used for authentication into the resource. If one of those claims held user information you could create an identity and assign the claims to it.
public void ValidateBearerToken(OwinContext context)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
byte[] securityKey = GetBytes("some key"); //this should come from a config file
SecurityToken securityToken;
var validationParameters = new TokenValidationParameters()
{
ValidAudience = "http://localhost:2000",
IssuerSigningToken = new BinarySecretSecurityToken(securityKey),
ValidIssuer = "Self"
};
var auth = context.Request.Headers["Authorization"];
if (!string.IsNullOrWhiteSpace(auth) && auth.Contains("Bearer"))
{
var token = auth.Split(' ')[1];
var principal = tokenHandler.ValidateToken(token, validationParameters, out securityToken);
context.Request.User = principal;
}
}
catch (Exception ex)
{
var message = ex.Message;
}
}
Related
I am new to .Net Core and have configured authentication as follow in Startup.cs file -
public void ConfigureServices(IServiceCollection services)
{
// To be able to access HttpContext
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(o => o.LoginPath = "/login");
// Most of the code removed for brevity
}
After user login, we authenticate the user by -
public static async Task AuthenticateUserAsync(HttpContext httpContext, AuthorizedUser authorizedUser)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, authorizedUser.UserUid.ToString()),
new Claim(CustomClaimTypes.CompanyGuid, authorizedUser.CompanyUid.ToString())
};
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties
{
// Refreshing the authentication session should be allowed.
//AllowRefresh = <bool>,
// The time at which the authentication ticket was issued.
IssuedUtc = DateTimeOffset.UtcNow,
// The time at which the authentication ticket expires. A
// value set here overrides the ExpireTimeSpan option of
// CookieAuthenticationOptions set with AddCookie.
ExpiresUtc = DateTimeOffset.UtcNow.AddHours(1),
// Whether the authentication session is persisted across
// multiple requests. When used with cookies, controls
// whether the cookie's lifetime is absolute (matching the
// lifetime of the authentication ticket) or session-based.
IsPersistent = false,
//RedirectUri = <string>
// The full path or absolute URI to be used as an http
// redirect response value.
};
await httpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties);
}
In some service class, we retrieve portal information and tried to save it in claims as follow -
public async Task AddPortalToCurrentUserClaimsAsync(Guid companyUid, Guid userUid)
{
var portal = await _unitOfWork.Portals.All().FirstOrDefaultAsync(p => p.CompanyUid == companyUid && p.UserUid == userUid).ConfigureAwait(false);
if (portal == null) return;
var claims = new List<Claim>
{
new Claim(CustomClaimTypes.PortalId, portal.Id.ToString()),
new Claim(CustomClaimTypes.PortalName, portal.Name)
};
var claimsIdentity = new ClaimsIdentity(claims);
_httpContextAccessor.HttpContext.User.AddIdentity(claimsIdentity);
}
When I tried to retrieve this claim in further requests, I get null.
var portalId = _httpContextAccessor.HttpContext.User.FindFirst(CustomClaimTypes.PortalId);
var portalName = _httpContextAccessor.HttpContext.User.FindFirst(CustomClaimTypes.PortalName);
How I can get these newly added claims to persist in further requests?
I read from the article that share
To create a cookie holding user information, construct a
ClaimsPrincipal. The user information is serialized and stored in the
cookie. SignInAsync creates an encrypted cookie and adds it to the
current response. If AuthenticationScheme isn't specified, the
default scheme is used.
So, you cannot add claims to already created ClaimsPrincipal as claims have already been stored in cookies.
The solution that work for me was to create new ClaimsPrincipal and create new cookie as -
public async Task AddPortalToCurrentUserClaimsAsync(Guid companyUid, Guid userUid)
{
var portal = await _unitOfWork.Portals.All().FirstOrDefaultAsync(p => p.CompanyUid == companyUid && p.UserUid == userUid).ConfigureAwait(false);
if (portal == null) return;
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, userUid.ToString()),
new Claim(CustomClaimTypes.CompanyGuid, companyUid.ToString()),
new Claim(CustomClaimTypes.PortalId, portal.Id.ToString()),
new Claim(CustomClaimTypes.PortalName, portal.Name)
};
var authProperties = new AuthenticationProperties
{
IssuedUtc = DateTimeOffset.UtcNow,
ExpiresUtc = DateTimeOffset.UtcNow.AddHours(1),
IsPersistent = false
};
const string authenticationType = "Cookies";
var claimsIdentity = new ClaimsIdentity(claims, authenticationType);
await _httpContextAccessor.HttpContext.SignInAsync(authenticationType, new ClaimsPrincipal(claimsIdentity), authProperties);
}
Claims are only added on persisted in the ClaimsPrinciple/User if SignInAsync has been called again. So, your first claims will be found because that are added before the user is signed in. However, as you're adding more claims, they won't be "saved" as the user's identity has not been reset.
If you're adding more claims, prior to the initial sign in, this should work:
var authProperties = new AuthenticationProperties
{
IssuedUtc = DateTimeOffset.UtcNow,
ExpiresUtc = DateTimeOffset.UtcNow.AddHours(1),
IsPersistent = false,
};
await httpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
_httpContextAccessor.HttpContext.User,
authProperties);
I'm building a ASP.NET Core (v3.1) module and I just managed to configure an OpenIdConnect Authentication. Now, I need to get all User's roles from an API in order to give or deny access to them, then I added multiple Claim values to the same Claim role "ClaimTypes.Role" in the User's Claims list through OnAuthorizationCodeReceived Event like so:
OnAuthorizationCodeReceived = async (context) =>
{
// Uses the authentication code and gets the access and refresh token
var client = new HttpClient();
var response = await client.RequestAuthorizationCodeTokenAsync(new AuthorizationCodeTokenRequest()
{
Address = urlServer + "/connect/token",
ClientId = "hybrid",
Code = context.TokenEndpointRequest.Code,
RedirectUri = context.TokenEndpointRequest.RedirectUri,
}
if (response.IsError) throw new Exception(response.Error);
var identity = new ClaimsIdentity(context.Principal.Identity);
var listRoles = GenericProxies.RestGet<List<string>>(urlGetRoles, response.AccessToken); // GET request to API
listRoles.ForEach(role => identity.AddClaim(new Claim(ClaimTypes.Role, role)));
context.HttpContext.User = new ClaimsPrincipal(identity);
context.HandleCodeRedemption(response.AccessToken, response.IdentityToken);
}
While debugging, I noticed all roles are added the the User's Claims list after this line:
context.HttpContext.User = new ClaimsPrincipal(identity);
But, apparently, in my Home controller (that is where the user is being redirected to, after authenticated), when I access HttpContext.User, I can't seem to find any of the roles I added before except for "Admin" (which I'm guessing is a default ClaimTypes.Role value).
[Authorize]
public IActionResult Index()
{
if (User.IsInRole("SomeRole"))
{
return RedirectToAction("SomeAction", "SomeController");
}
else
{
return RedirectToAction("Forbidden", "Error");
}
}
Reading some other posts forums and topics, I found that this is probably a context persistence issue, which I tried to solve with this code in my Account controller:
public async Task Login(string returnUrl = "/")
{
await HttpContext.ChallengeAsync(
"OIDC",
new AuthenticationProperties
{
AllowRefresh = false,
IsPersistent = true,
RedirectUri = returnUrl
});
}
Some examples said that I could use context.Principal.AddIdentity(identity); in order to persist the new Claims list, but then I got the following error:
InvalidOperationException: only a single identity supported
IdentityServer4.Hosting.IdentityServerAuthenticationService.AssertRequiredClaims(ClaimsPrincipal principal)
Summing up, I must find a way to persist the role claims I added to the User's Claims list but I got no success until now.
Update on that, if that's useful to anyone.
I deduced that the problem whas with context.HttpContext.User = new ClaimsPrincipal(identity);, which I understood previously to be the part of the code that handled the persistence of the new claims.
Actually, I did noticed there was a context.Principal attribute of the type ClaimsPrincipal, and looked like it was the actual Current context, so I digged into it and tried to figure out a way to add elements to its "IEnumerable<Claim> Claims" attribute which is readonly.
After a while, I found the following solution that worked just fine for me:
Instead of
var identity = new ClaimsIdentity(context.Principal.Identity);
var listRoles = GenericProxies.RestGet<List<string>>(urlGetRoles, response.AccessToken); // GET request to API
listRoles.ForEach(role => identity.AddClaim(new Claim(ClaimTypes.Role, role)));
I tried
var identity = context.Principal.Identity as ClaimsIdentity;
if(identity != null)
{
var listRoles = GenericProxies.RestGet<List<string>>(urlGetRoles, response.AccessToken); // GET request to API
foreach (var role in listRoles)
{
identity.AddClaim(new Claim(ClaimTypes.Role, role));
}
}
So I have an api with create, login and so on using JWT. Right now my Login class is far too wide and with too much responsibility for my likings. Here it is;
[HttpPost("login")]
public async Task<IActionResult> Login(UserForLoginDto user)
{
var userFromRepo = await _qrepo.Login(user.Username, user.Password);
//IF no user found in db
if (userFromRepo == null)
//Return unauth so if user have wrong login creds, we're not specifying if it's password or username
return Unauthorized();
//Token creation
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, userFromRepo.Id.ToString()),
new Claim(ClaimTypes.Name, userFromRepo.Username)
};
// Hashed token Key
// The token is unique and very secret - if you have the token you are able to create tokens that are verifyable for our backend
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config.GetSection("AppSettings:Token").Value));
// Signing credentials
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
// Security Token DEscripter
var tokenDescriptor = new SecurityTokenDescriptor
{
// our claims
Subject = new ClaimsIdentity(claims),
// Expiry date - 1 day from create
Expires = DateTime.Now.AddDays(1),
SigningCredentials = creds
};
// Token handler
var tokenHandler = new JwtSecurityTokenHandler();
// Actual token
var token = tokenHandler.CreateToken(tokenDescriptor);
// Return actual token
return Ok(new
{
token = tokenHandler.WriteToken(token)
});
}
I would like my token creation seperated from my Controller methods but I'm not entirely sure what is the best approach to this so where would this belong? This is my folder structure:
I don't feel like it belongs in either of my folders but maybe in helpers, idk? What is the standard approach to this?
Could you just put all the token generation stuff into a separate service class?
Then use DI to inject the service.
Also give it an interface so you can test it more easily:
public interface IJwtTokenGenerator
{
string GenerateToken(User user);
}
public class JwtTokenGenerator : IJwtTokenGenerator
{
private readonly IConfiguration _config;
public JwtTokenGenerator(IConfiguration config)
{
_config = config;
}
//obviously, change User to whatever your user class name is
public string GenerateToken(User user)
{
//Token creation
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username)
};
// Hashed token Key
// The token is unique and very secret - if you have the token you are able to create tokens that are verifyable for our backend
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config.GetSection("AppSettings:Token").Value));
// Signing credentials
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
// Security Token DEscripter
var tokenDescriptor = new SecurityTokenDescriptor
{
// our claims
Subject = new ClaimsIdentity(claims),
// Expiry date - 1 day from create
Expires = DateTime.Now.AddDays(1),
SigningCredentials = creds
};
// Token handler
var tokenHandler = new JwtSecurityTokenHandler();
// Actual token
var securityToken = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(securityToken);
}
}
Then your Login action might look like:
[HttpPost("login")]
public async Task<IActionResult> Login(UserForLoginDto user)
{
var userFromRepo = await _qrepo.Login(user.Username, user.Password);
//IF no user found in db
if (userFromRepo == null)
//Return unauth so if user have wrong login creds, we're not specifying if it's password or username
return Unauthorized();
//Injected ITokenGenerator (note the interface)
var token = _tokenGenerator.GenerateToken(userFromRepo);
// Return actual token
return Ok(new
{
token
});
}
In terms of what folder to put this class and interface (should be two separate files), it mostly depends on what makes sense for you or your team. Maybe another folder called "Services", maybe "Authentication", maybe "Authentication/Services". "Helpers" are generally for static classes, but you could arguably put it in there I guess.
I have implemented the authentication with OWIN and bearer token and it works fine when the user login.
When \Token URL is called and username/password is passed to it, that gives token in response. But I would like to store this token in Database so instead of making another call to the server can I get the token in code? I am not able to get the value of the generated token in the ticket or any other object.
public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
return Task.Factory.StartNew(() =>
{
var username = context.UserName;
var password = context.Password;
var userService = new UserService();
User user = userService.GetUserByCredentials(username, password);
if (user != null)
{
var claims = new List<Claim>()
{
new Claim(ClaimTypes.Name, user.userName),
new Claim("UserID", user.userName)
};
ClaimsIdentity oAutIdentity = new ClaimsIdentity(claims, Startup.OAuthOptions.AuthenticationType);
var ticket = new AuthenticationTicket(oAutIdentity, new AuthenticationProperties() { });
context.Validated(ticket);
}
else
{
context.SetError("invalid_grant", "Error");
}
});
}
I am debugging the code but surprisingly the access_token seems to be visible nowhere only getting it in postman results.
The token is not valid forever. A new token is given for every authentication and is valid for a set amount of time. There is no use in saving this token to the database.
Sure you can.
You just need to override the method TokenEndpointResponseinside your authServerProvider : OAuthAuthorizationServerProvider.
Inside OAuthTokenEndpointResponseContext, there is a field called accessToken that you can retrieve the token value.
public override Task TokenEndpointResponse(OAuthTokenEndpointResponseContext context)
{
// Summary:
// Called before the TokenEndpoint redirects its response to the caller.
return Task.FromResult<object>(null);
}
I'm trying to create Jwt token authorization. For this purpose I have issuer part with the code like that:
public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] {"*"});
Users user;
using (var db = new UserStore())
{
user = Task.Run(()=> db.FindUser(context.UserName, context.Password, context.ClientId)).Result;
}
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect");
return Task.FromResult<object>(null);
}
var identity = new ClaimsIdentity("JWT");
identity.AddClaim(new Claim(ClaimTypes.Name, user.Email));
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim(ClaimTypes.Role, user.Roles.Name));
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"audience", context.ClientId ?? string.Empty
}
});
var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);
return Task.FromResult<object>(null);
}
And "resource" part that should accept bearer token:
public void ConfigureOAuth(IAppBuilder app)
{
var issuer = SiteGlobal.Issuer;
var audience = SiteGlobal.Audience;
var secret = TextEncodings.Base64Url.Decode(SiteGlobal.Secret);
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audience },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
}
});
}
As far as I can see issued token are valid (I did validation on jwt.io), so the problem is somehwere else. When I'm sending token in Postman with the call to controller protected by [Authorize] attribute it always return 401 code. Could you please advise how to fix this?
P.S. This is how I implement custom Jwt fortmat:
public string Protect(AuthenticationTicket data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
string audienceId = data.Properties.Dictionary.ContainsKey(AudiencePropertyKey) ? data.Properties.Dictionary[AudiencePropertyKey] : null;
if (string.IsNullOrWhiteSpace(audienceId)) throw new InvalidOperationException("AuthenticationTicket.Properties does not include audience");
Audience audience;
using (var store = new AudienceStore())
{
audience = Task.Run(()=> store.FindAudience(audienceId)).Result;
}
var symmetricKeyAsBase64 = audience.Base64Secret;
var signingKey = new InMemorySymmetricSecurityKey(Encoding.UTF8.GetBytes(symmetricKeyAsBase64));
var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256Signature, SecurityAlgorithms.Sha256Digest);
var issued = data.Properties.IssuedUtc;
var expires = data.Properties.ExpiresUtc;
var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingCredentials);
var handler = new JwtSecurityTokenHandler();
var jwt = handler.WriteToken(token);
return jwt;
}
P.S. Guys, I'm so sorry, but I forgot to explain that "issuer" part of code that's standalone application, meanwhile "audience" is protected web api. That's two different appliactions running independently.
In Postman ensure you are sending the authorization header using the following format:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
Ensure that you leave the Authorization tab set to Type: No Auth.
If you continue to have issues, set a breakpoint in your GrantResourceOwnerCredentials and see if it gets to that point. Also consider overriding the ValidateClientAuthentication method of OAuthAuthorizationServerProvider which should get called prior to GrantResourceOwnerCredentials if you want to debug earlier in the chain of events.
I have just tried to run demo project mentioned in SON Web Token in ASP.NET Web API 2 using Owin and all worked as expected.
I noticed that your implementation of Protect method differs quite a bit. I would suggest you to compare your implementation to an example given in the article. Try make that work first.
Also please make sure that issuer, audience and secret are same on both servers.
If you provide complete source code I can try to investigate more.