I've logged into an external provider (Google) which returned me an access token.
My external call back calls this code to get the user claims:
public async Task<IActionResult> ExternalLoginCallback(string returnUrl)
{
// read external identity from the temporary cookie
var info = await HttpContext.Authentication.GetAuthenticateInfoAsync(
IdentityServerConstants.ExternalCookieAuthenticationScheme);
var tempUser = info?.Principal;
if (tempUser == null)
{
throw new Exception("External authentication error");
}
// retrieve claims of the external user
var claims = tempUser.Claims.ToList();
//Do more work
}
Now When I logout of Identity Server 4 and log back in, It does not make another request for consent. Yet all of my claims are still there, where does identity server store these or are they being retrieved from the cookie some how?
Related
I have a Razor Pages app developed using .NET Core 6. The app works as a client and connects to an API. The API has JWT Access Token/Refresh Token authentication. The login endpoint of the API returns access token and refresh token.Using cookie authentication I store the tokens as claim in authentication cookie.
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, Login.Email),
new Claim("Token", loginResponse.Token),
new Claim("RefreshToken", loginResponse.RefreshToken)
};
I then get the tokens using extension methods
public static class CommonExtensions
{
public static string? GetToken(this HttpContext context)
{
return context.User.Claims.Single(x => x.Type == "Token").Value.ToString();
}
public static string? GetRefreshToken(this HttpContext context)
{
return context.User.Claims.Single(x => x.Type == "RefreshToken").Value.ToString();
}
}
When my access token expires I refresh it, remove existing claims and add new ones with the updated token.
var identity = User.Identity as ClaimsIdentity;
identity.RemoveClaim(identity.FindFirst("Token"));
identity.AddClaim(new Claim("Token", response.Token));
identity.RemoveClaim(identity.FindFirst("RefreshToken"));
identity.AddClaim(new Claim("RefreshToken", response.RefreshToken));
However the subsequent requests keep using the expired token. What is the way to update the claims correctly?
In order to save your changes you would need to call SignInAsync according to Microsoft.
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, User);
If it does not work, maybe you need to call SignOutAsync first.
I have a React project with API (net core). My website menus/fields will be shown/hidden based on the Role of the user. The user will login to my website via external oidc.
However, the access_token and id_token coming from the oidc doesn't have the Role information, it will only have their email, which I will use to check against my Database to determine which Role is the logged in user. Currently I have an API to get Role based on their access_token, so it's something like
public string getRoles(string access_token)
{
//check Database
return role;
}
This function will be called in almost every page so I was wondering is there any more efficient way to do this?
You need to add the claim to the HttpContext.User when the signin is confirmed with the role from the DB. When you define this connection in your startup, be sure to handle the OnTokenValidated event.
.AddOpenIdConnect("oidc", options =>
{
options.Events = new OpenIdConnectEvents
{
OnTokenValidated = async ctx =>
{
var claim = new Claim("Role", "TheirRole");
var identity = new ClaimsIdentity(new[] { claim });
ctx.Principal.AddIdentity(identity);
await Task.CompletedTask;
}
};
}
Then you can access this within the controller (or anywhere with HttpContext) like so
var claim = HttpContext.User.Claims.First(c => c.Role == "TheirRole");
Introduction
I've been working for the last couple of days on small pet-project with a goal of learning .NET Core 2.0 with Identity backed by Entity Framework Core. It is a typical "WebAPI" type project with cookie based authentication and claims based authorization. It is utilized by some client application (SPA).
Code
Authorization & Authentication flow is configured this way in Startup.cs
services
.AddIdentity<ApplicationUser, IdentityRole> ()
.AddEntityFrameworkStores<ApplicationDbContext> ()
.AddDefaultTokenProviders ();
services
.AddAuthentication (sharedOptions => {
sharedOptions.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie ();
My login controller action looks like this:
[HttpPost]
[Route ("login")]
public async Task<IActionResult> Login ([FromBody] LogInCredentialsModel credentials) {
// Get User for given UserName
var user = await userManager.Users.FirstOrDefaultAsync (p => p.UserName == credentials.UserName);
//User not found
if (user == default (ApplicationUser))
return StatusCode (400);
// Check if password is correct
var result = await signInManager.PasswordSignInAsync (user, credentials.Password, true, false);
if (result.Succeeded) {
//Basic claims with Name and Email
List<Claim> claims = new List<Claim> {
new Claim (ClaimTypes.Name, user.UserName),
new Claim (ClaimTypes.Email, user.Email)
};
var userRoles = await this.GetUserRoles (user); // Custom helper method to get list of user roles
// Add Role claims
foreach (var role in userRoles) {
claims.Add (new Claim (ClaimTypes.Role, role));
}
ClaimsIdentity identity = new ClaimsIdentity (claims, CookieAuthenticationDefaults.AuthenticationScheme);
ClaimsPrincipal principal = new ClaimsPrincipal (identity);
// Sign in using cookie scheme
await HttpContext.SignInAsync (CookieAuthenticationDefaults.AuthenticationScheme, principal, new AuthenticationProperties {
IsPersistent = true,
});
return Ok ();
} else {
return StatusCode (400);
}
}
Problems
These claims will be stored in encrypted user cookie. This means, that if I remove some claim from user and he does not re-log, he will still have old claims assigned. How do I prevent that? Or did I misunderstand the design?
User passes UserName and Password to login route which is then used to sign him in. In my code, I have to first find user with given UserName (1st db hit), then try to sigin in with password using SignInManager (2nd db hit), read roles (3rd db hit) to build ClaimsPrincipal and then use HttpContext.SignInAsync so user cookie with correct claims is created. I personally feel like I'm missing something and in the result my code is overcomplicated, also atleast one of the database queries could be saved here. How to improve this part?
The answers to both of your questions are pretty basic, so maybe you should spend some more time with the docs to get a better handle on this. That said:
Yes. You are correct. When you change a claim, you should sign the user out as well. Then, you can choose to automatically sign them in again, without user intervention, or prompt the user to re-login (depending on your personal security preferences).
Why are you doing all this manually? All you need is:
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
That automatically hashes the password, attempts to retrieve the user with that username (email address) and hashed password, and then creates a ClaimsPrincipal with all that information, if successful. One and done.
I have an ASP.Net application that uses OWIN and External logins through a third party provider, specifically google.
During Authentication, this code is used to pull the ClaimsIdentity from the OwinContext
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
where AuthenticationManager is
private IAuthenticationManager AuthenticationManager
{
get
{
return HttpContext.GetOwinContext().Authentication;
}
}
However, on subsequent requests (i.e. redirecting to the home page after successful login) the GetExternalLoginInfoAsync() returns null. What I want to do is access information about the user's account (i.e. profile picture) that the external provider returns from my auth request. How can I access these Claims from an MVC controller or a Web API controller?
Almost all of the code I have is Visual Studio boiler plate code, so I won't include it here, but I can add some if anything specific is needed.
Thanks for any help you can offer.
I was trying to use the external login info to pull data such as picture and profile url. The correct way to do this is to assign claims from the external source to the local identity as so:
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<User, string> manager, IAuthenticationManager authentication = null)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
if (authentication != null)
{
ExternalLoginInfo info = authentication.GetExternalLoginInfo();
if (info != null)
foreach (Claim claim in info.ExternalIdentity.Claims.Where(claim => !userIdentity.HasClaim(c => c.Type == claim.Type)))
{
userIdentity.AddClaim(claim);
await manager.AddClaimAsync(userIdentity.GetUserId(), claim);
}
}
return userIdentity;
}
After testing the build-in MVC 5 OAuth2/OpenID providers I was able to create a website which allowed me authenticate myself using my Twitter credentials.
The problem I now encounter is that I also want to store the tokens (oauth_token & oauth_verifier) Twitter posts back, in the url, after a user has been successfully authenticated. I need these tokens so I can allow users to post details directly from my website to their twitter account.
After setting up the TwitterAuthenticationOptions (see below) in the Startup.Auth.cs I did found that the tokens that I'm after can be found in the context (((context.Response.Context).Request).QueryString) but parsing this seems an ugly solution.
var tw = new TwitterAuthenticationOptions {
ConsumerKey = "SecretKey",
ConsumerSecret = "SecretSecret",
SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie,
Provider = new TwitterAuthenticationProvider() {
OnAuthenticated = (context) => {
context.Identity.AddClaim(new System.Security.Claims.Claim("urn:twitter:access_token", context.AccessToken, XmlSchemaString, "Twitter"));
return Task.FromResult(0);
}
}
};
app.UseTwitterAuthentication(tw);
How can this gracefully be implemented? For Facebook I found a solution which actually retrieves additional information, this feel similar...
get-more-information-from-social-providers-used-in-the-vs-2013-project-templates
There is a good extension method in Request object. Add following lines in HomeController or controller wherever needed.
Request.GetOwinContext().Authentication.User.Claims // Lists all claims
// Filters by type
Request.GetOwinContext().Authentication.User.FindAll("urn:twitter:access_token")
GetOwinContext will give you the Authentication object where you can find the user object and them the claims.
I found a useful post here How do I access Microsoft.Owin.Security.xyz OnAuthenticated context AddClaims values?
I modified as mentioned in the steps in the post.
AccountController.cs
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
//New method call made here to persist the claims from external cookie
await SetExternalProperties(identity);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
//New method added to persist identity info
private async Task SetExternalProperties(ClaimsIdentity identity)
{
// get external claims captured in Startup.ConfigureAuth
ClaimsIdentity ext = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
if (ext != null)
{
var ignoreClaim = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims";
// add external claims to identity
foreach (var c in ext.Claims)
{
if (!c.Type.StartsWith(ignoreClaim))
if (!identity.HasClaim(c.Type, c.Value))
identity.AddClaim(c);
}
}
}
try this and let me know.
You can use Query instead of QueryString and then use Get method to retrieve value from query string.
context.Response.Context.Request.Query.Get("oauth_verifier");
context.Response.Context.Request.Query.Get("oauth_token"); or context.AccessToken
Other thining to note is that you shouldn't need oauth_verifer to post data. Look here at required headers. I suggest you to use one of libraries listended here to interact with Twitter.