Closed arthas1888 closed 5 years ago
Context.UserIdentifier
will be null unless you have Authentication in your app. You can read about Authentication and Authorization at https://docs.microsoft.com/aspnet/core/signalr/authn-and-authz?view=aspnetcore-2.2.
By default SignalR will set UserIdentifier
to the users ClaimType.NameIdentifier
. You can override that behavior by providing your own implementation of IUserIdProvider
in startup. e.g. services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
There is an example of this in the doc I linked above.
I did that, my users are authenticated, because my hub is not public
How are you doing authentication? Do authenticated users have a NameIdentifier
claim?
I use authentication with openiddict core, and I put that claim in the ClaimsPrincipal when the token is being created
Could you provide a sample app that demonstrates the problem?
Or could you put the following code in your Configure
method:
app.UseAuthorization();
app.Use(next =>
{
return context =>
{
return next(context);
};
});
And debug the app, putting a breakpoint on return next(context);
, then viewing context.User.Identities[0].Claims
and showing what claims are there?
For example:
Thank you for your help, at last it works but by default Context.UserIdentifier always is null, I had put this code:
public class NameUserIdProvider : IUserIdProvider
{
public virtual string GetUserId(HubConnectionContext connection)
{
return connection.User?.Identity?.Name;
}
}
After that Context.UserIdentifier has value, so is it correct? Always I need add to the project the following code:
services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
this is my claims:
Huh, that is very odd, you have the NameIdentifier
claim, so the default UserIdentifier
shouldn't be null.
Could you share your Configure
method? I'd like to see the order of your middleware.
this is my startup methods:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContextPool<ApplicationDbContext>(options =>
{
options.UseNpgsql(Configuration.GetConnectionString("PsqlConnection"), o => o.UseNetTopologySuite());
//options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
options.UseOpenIddict();
}
);
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddDefaultAWSOptions(Configuration.GetAWSOptions());
services.AddAWSService<IAmazonS3>();
services.Configure<BookstoreDatabaseSettings>(
Configuration.GetSection(nameof(BookstoreDatabaseSettings)));
services.AddSingleton<IBookstoreDatabaseSettings>(sp =>
sp.GetRequiredService<IOptions<BookstoreDatabaseSettings>>().Value);
services.AddMvc()
.AddJsonOptions(options =>
{
options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver();
//options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSignalR();
// Configure Identity to use the same JWT claims as OpenIddict instead
// of the legacy WS-Federation claims it uses by default (ClaimTypes),
// which saves you from doing the mapping in your authorization controller.
services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserNameClaimType = OpenIdConnectConstants.Claims.Name;
options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject;
options.ClaimsIdentity.RoleClaimType = OpenIdConnectConstants.Claims.Role;
});
// Adds services required for using options.
services.AddOptions();
services.AddMemoryCache();
services.AddScoped<IRepository<ApplicationUser>, UsersManager>();
services.AddScoped<IRepository<ApplicationRole>, RolesManager>();
services.AddScoped<IRepository<IdentityRoleClaim<string>>, ClaimsManager>();
services.AddScoped<IRepository<Permission>, GenericModelFactory<Permission>>();
services.AddScoped<IRepository<Audit>, AuditManager>();
services.AddScoped<IRepository<CommonOption>, GenericModelFactory<CommonOption>>();
services.AddScoped<IRepository<Brand>, BrandManager>();
services.AddScoped<IRepository<Model>, ModelManager>();
services.AddScoped<IRepository<Vehicle>, GenericModelFactory<Vehicle>>();
services.AddScoped<IRepository<Journey>, JourneyManager>();
//services.AddSingleton<MongoCRUDService<Chat>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new Info
{
Version = "v1.0.0",
Title = "Carpooling API 2019",
Description = "",
TermsOfService = "None",
Contact = new Contact { Name = "Ser Soluciones SAS", Email = "contacto@sersoluciones.com", Url = "https://www.sersoluciones.com/" },
});
options.DescribeAllEnumsAsStrings();
});
// add OpenIddict
services.AddOpenIddict()
.AddCore(options =>
{
// AddEntityFrameworkCoreStores() is now UseEntityFrameworkCore().
options.UseEntityFrameworkCore()
.UseDbContext<ApplicationDbContext>();
})
.AddServer(options =>
{
// AddMvcBinders() is now UseMvc().
options.UseMvc();
options.EnableAuthorizationEndpoint("/connect/authorize")
.EnableLogoutEndpoint("/connect/logout")
.EnableTokenEndpoint("/connect/token")
.EnableUserinfoEndpoint("/api/userinfo");
options.AllowAuthorizationCodeFlow()
.AllowClientCredentialsFlow()
.AllowPasswordFlow()
.AllowRefreshTokenFlow();
options.RegisterScopes(OpenIdConnectConstants.Scopes.Email,
OpenIdConnectConstants.Scopes.Profile,
OpenIddictConstants.Scopes.Roles);
// This API was removed as client identification is now
// required by default. You can remove or comment this line.
//
// options.RequireClientIdentification();
options.EnableRequestCaching();
// This API was removed as scope validation is now enforced
// by default. You can safely remove or comment this line.
//
// options.EnableScopeValidation();
options.SetAccessTokenLifetime(TimeSpan.FromDays(10));
options.DisableHttpsRequirement();
});
services.AddAuthentication(options =>
{
options.DefaultScheme = OAuthValidationDefaults.AuthenticationScheme;
})
.AddOAuthValidation(options =>
{
options.Events.OnRetrieveToken = context =>
{
context.Token = context.Request.Query["access_token"];
return Task.CompletedTask;
};
})
.AddGoogle(options =>
{
options.ClientId = "200532511210-srh778iqpokebpj435lcs3ldf9lshboh.apps.googleusercontent.com";
options.ClientSecret = "L-YjJ3dfq-HWO_mN-lGmn4EK";
});
// Add application services.
services.AddSingleton<IEmailSender, AuthMessageSender>();
//services.AddSingleton<ISmsSender, AuthMessageSender>();
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequiredLength = 6;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = true;
});
services.Configure<GzipCompressionProviderOptions>(options => options.Level = System.IO.Compression.CompressionLevel.Optimal);
services.AddResponseCompression();
services.AddCors();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment() || env.IsStaging())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseResponseCompression();
//app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseCors(options =>
{
// this defines a CORS policy called "default"
options
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
app.UseAuthentication();
app.UseMvcWithDefaultRoute();
#region UseSwagger
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Carpooling API V1");
c.DocExpansion(DocExpansion.None);
});
#endregion
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/chatHub");
});
//DBContextSeedData.InitializeAsync(app.ApplicationServices, CancellationToken.None).GetAwaiter().GetResult();
//DBContextSeedData.SeedRoles(app.ApplicationServices).GetAwaiter().GetResult();
//DBContextSeedData.SeedPermissions(app.ApplicationServices).GetAwaiter().GetResult();
//DBContextSeedData.CreateSuperUser(app.ApplicationServices).GetAwaiter().GetResult();
Console.WriteLine($"Starting Carpooling API, Db in {Configuration["ConnectionStrings:PsqlConnection"]}");
}
Thanks for sharing.
If the options.ClientId = "XYZ";
and options.ClientSecret = "ZYX";
are secrets and not meant to be shared, make sure you change your secrets because they are posted in the code snippet.
@Tratcher Can you see anything obvious as to why User?.FindFirst(ClaimTypes.NameIdentifier)?.Value
would be null in SignalR based on the above Startup
?
@arthas1888 provided a screenshot that also shows the NameIdentifier
claim in the Claims
.
@arthas1888 Could you load symbols while debugging for "Microsoft.AspNetCore.SignalR.Core" and add a function breakpoint (ctrl-k, b) with the "Function Name:" set as "Microsoft.AspNetCore.SignalR.DefaultUserIdProvider.GetUserId" then connect with a client and see why the NameIdentifier
can't be found?
OK, is strange now it works, before when I deleted this line in my startup services.AddSingleton<IUserIdProvider, NameUserIdProvider>(); the hub couldn't find the NameIdentifier but now it can, so I have another question, when I use Authentication under Cookie How does .net Core set this ClaimType.NameIdentifier?
Well whoever created the Cookie will have set some claims on it before giving it to the client and will know how to read those claims when the client sends the cookie back.
See the docs for an example of creating a cookie with some claims.
Thank you so much, your help has been very useful
You're welcome, glad to help :)
Can we close the issue?
yes, please :)
I've tried to test Users in SignalR however this param always is empty, where do I set this one?
Document Details
⚠ Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.