New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
User.GetUserId() doesn't work while using token authentication #15
Comments
Hi @mypcisbetter - I'm about 12,000 miles away from my computer at the moment, I'll have a look at this when I get back but it'll be at least two weeks before that. |
I'm having the same issue. Also, once I requested a token to the controller which is sent successfully to another WebAPI, Authorize wont work, probably because I need to set that information in the HttpContext that is where I am unsure what to do. |
This example doesn't really work with the Identity system, and therefore not User.GetUserId() won't work. |
Good morning guys, the problem here is that you are not setting up the identity claim correctly. GetUserId is an extension method. If you check GetUserId source code you will see that the method basically looks for a claim called "ClaimTypes.NameIdentifier" (principal.FindFirstValue(ClaimTypes.NameIdentifier); ) So the solution to this is to add that claim in ClaimsIdentity in the TokenController, method GetToken after the comment eg:
Then in my CategoryController:
Hope this can help. This is how I solved it. |
Thanks @vackup. I think it solves the problem. |
@vackup It seems like there is a problem somewhere... It do work after running my web app, but after some time GetUserId() becomes NULL again. Token that I use is exactly the same, everything on client side seems to be right. I'm making the same request each time. It works some time (usually 4-5 calls) and then it suddenly stop working (I mean: on server side GetUserId() returns null instead of id). I suspected that it was something like token expiration, but it gives me access to data every time (so token works (and expirtation is set to half an hour so no, it isn't token expiration)), just Id is null... Is there something I don't know about this code? Why do you think it happens? |
@MyPCIsBetter mmmmm, that's weird because if token has expired, then you wouldn't be able to access your data. Maybe you are refreshing the token somewhere and when creating the new token you are not setting up the userId correclty as you are doing the first time the token was created:
May be if you share your code we can try to figure out the issue. By the way, asp.net 5 is still in RC, I mean it's not the final version so maybe there is a bug somewhere |
@vackup public class TokenAuthManager
{
public string Audience { get; set; }
public string Issuer { get; set; }
public SigningCredentials SigningCredentials { get; set; }
public string TokenGenerator(string userId, string user, DateTime? expires)
{
var handler = new JwtSecurityTokenHandler();
// Here, you should create or look up an identity for the user which is being authenticated.
// For now, just creating a simple generic identity.
ClaimsIdentity identity = new ClaimsIdentity(
new GenericIdentity(user, "TokenAuth"),
new[] {
new Claim("EntityID", "1", ClaimValueTypes.Integer),
new Claim(ClaimTypes.NameIdentifier, userId)
});
//identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userId));
var securityToken = handler.CreateToken(
issuer: Issuer,
audience: Audience,
signingCredentials: SigningCredentials,
subject: identity,
expires: expires
);
return handler.WriteToken(securityToken);
}
} This is when I create token (this is controler. I send token to the View using ViewData and there I assign it to javascript variable. I know it's weird what I've done here, I'll change it later): [Authorize]
public IActionResult Notepad()
{
//var a = db.Notes.Include(x => x.NoteTags).ToList();
var a = User.GetUserId();
ViewData["AuthToken"] = _tokenOptions.TokenGenerator(User.GetUserId(), User.Identity.Name, DateTime.UtcNow.AddMinutes(60));
return View();
} Entire Startup.cs file (no big difference between what is in mrsheepuk's github repo, I just copy/pasted to simply make it work) : public class Startup
{
const string TokenAudience = "ExampleAudience";
const string TokenIssuer = "ExampleIssuer";
private RsaSecurityKey key;
private TokenAuthManager tokenOptions;
public Startup(IHostingEnvironment env)
{
// Set up configuration sources.
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
if (env.IsDevelopment())
{
// For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709
builder.AddUserSecrets();
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<AuthorizationDbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:AuthConnectionString"]))
.AddDbContext<DataDbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:DataConnectionString"]));
services.AddIdentity<ApplicationUser, IdentityRole>(o =>
{
// configure identity options
o.Password.RequireDigit = false;
o.Password.RequireLowercase = false;
o.Password.RequireUppercase = false;
o.Password.RequireNonLetterOrDigit = false; ;
o.Password.RequiredLength = 6;
})
.AddEntityFrameworkStores<AuthorizationDbContext>()
.AddDefaultTokenProviders();
services.AddScoped<MySignInManager<ApplicationUser>, MySignInManager<ApplicationUser>>(); //rzekomo dzięki temu można zastąpić SignInManagera swoim własnym
//Token-based authentication https://github.com/mrsheepuk/ASPNETSelfCreatedTokenAuthExample
// *** CHANGE THIS FOR PRODUCTION USE ***
// Here, we're generating a random key to sign tokens - obviously this means
// that each time the app is started the key will change, and multiple servers
// all have different keys. This should be changed to load a key from a file
// securely delivered to your application, controlled by configuration.
//
// See the RSAKeyUtils.GetKeyParameters method for an examle of loading from
// a JSON file.
RSAParameters keyParams = RSAKeyUtils.GetRandomKey();
// Create the key, and a set of token options to record signing credentials
// using that key, along with the other parameters we will need in the
// token controlller.
key = new RsaSecurityKey(keyParams);
tokenOptions = new TokenAuthManager()
{
Audience = TokenAudience,
Issuer = TokenIssuer,
SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.RsaSha256Signature)
};
// Save the token options into an instance so they're accessible to the
// controller.
services.AddInstance<TokenAuthManager>(tokenOptions);
// Enable the use of an [Authorize("Bearer")] attribute on methods and classes to protect.
services.AddAuthorization(auth =>
{
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser().Build());
});
//Koniec Token-based authentication
services.AddMvc();
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// For more details on creating database during deployment see http://go.microsoft.com/fwlink/?LinkID=615859
try
{
using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>()
.CreateScope())
{
serviceScope.ServiceProvider.GetService<AuthorizationDbContext>()
.Database.Migrate();
}
}
catch { }
}
/*
* Źródło: https://github.com/mrsheepuk/ASPNETSelfCreatedTokenAuthExample
*/
// Register a simple error handler to catch token expiries and change them to a 401,
// and return all other errors as a 500. This should almost certainly be improved for
// a real application.
app.UseExceptionHandler(appBuilder =>
{
appBuilder.Use(async (context, next) =>
{
var error = context.Features[typeof(IExceptionHandlerFeature)] as IExceptionHandlerFeature;
// This should be much more intelligent - at the moment only expired
// security tokens are caught - might be worth checking other possible
// exceptions such as an invalid signature.
if (error != null && error.Error is SecurityTokenExpiredException)
{
context.Response.StatusCode = 401;
// What you choose to return here is up to you, in this case a simple
// bit of JSON to say you're no longer authenticated.
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(
JsonConvert.SerializeObject(
new { authenticated = false, tokenExpired = true }));
}
else if (error != null && error.Error != null)
{
context.Response.StatusCode = 500;
context.Response.ContentType = "application/json";
// TODO: Shouldn't pass the exception message straight out, change this.
await context.Response.WriteAsync(
JsonConvert.SerializeObject
(new { success = false, error = error.Error.Message }));
}
// We're not trying to handle anything else so just let the default
// handler handle.
else await next();
});
});
app.UseJwtBearerAuthentication(options =>
{
// Basic settings - signing key to validate with, audience and issuer.
options.TokenValidationParameters.IssuerSigningKey = key;
options.TokenValidationParameters.ValidAudience = tokenOptions.Audience;
options.TokenValidationParameters.ValidIssuer = tokenOptions.Issuer;
// When receiving a token, check that we've signed it.
options.TokenValidationParameters.ValidateSignature = true;
// When receiving a token, check that it is still valid.
options.TokenValidationParameters.ValidateLifetime = true;
// This defines the maximum allowable clock skew - i.e. provides a tolerance on the token expiry time
// when validating the lifetime. As we're creating the tokens locally and validating them on the same
// machines which should have synchronised time, this can be set to zero. Where external tokens are
// used, some leeway here could be useful.
options.TokenValidationParameters.ClockSkew = TimeSpan.FromMinutes(0);
});
//koniec kodu z tokenami
app.UseIISPlatformHandler(options => options.AuthenticationDescriptions.Clear());
app.UseStaticFiles();
app.UseIdentity();
// To configure external authentication please see http://go.microsoft.com/fwlink/?LinkID=532715
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Notepad}/{id?}");
});
}
// Entry point for the application.
public static void Main(string[] args) => WebApplication.Run<Startup>(args);
}
} |
User.GetUserId();
always returns null when I use controller with [Authorize("Bearer")] attribute. For not-api controllers (with [Authorize] everything seems to work OK).
I suspect that there is something to include in [ClaimsIdentity object here].
Could you help me with solving this?
The text was updated successfully, but these errors were encountered: