I'm trying to allow users to log in with their Google account on my ASP.NET Core Blazor app. Whenever I go to my app/login/google-login, everything works as expected. I get redirected to google's login page and I get to choose an account to log in with. After choosing my account, it takes a few seconds to load and then visual studio 2019 tells me this:
System.NullReferenceException: 'Object reference not set to an instance of an object.' Microsoft.AspNetCore.Authentication.AuthenticateResult.Principal.get returned null.
at this block of code:
var claims = response.Principal.Identities.FirstOrDefault().Claims.Select(claim => new
{
claim.Issuer,
claim.OriginalIssuer,
claim.Type,
claim.Value
});
Some debugging has revealed the following:
{"succeeded":false,"ticket":null,"principal":null,"properties":null,"failure":null,"none":true}
This is the response I get from Google's API formatted as JSON. Basically it tells me that the principal is null, which I could have guessed, but the rest is also null. What's going on here? Could this simply be an issue with my scopes on Google's end? I have reasons to believe this isn't the problem though since my app should be able to work with any API response without crashing, right?
Here's my LoginController.cs class:
[AllowAnonymous, Route("login")]
public class LoginController : Controller
{
[Route("google-login")]
public IActionResult GoogleLogin()
{
var properties = new AuthenticationProperties { RedirectUri = Url.Action("GoogleResponse") };
return Challenge(properties, GoogleDefaults.AuthenticationScheme);
}
[Route("google-response")]
public async Task<IActionResult> GoogleResponse()
{
var response = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
var claims = response.Principal.Identities.FirstOrDefault().Claims.Select(claim => new
{
claim.Issuer,
claim.OriginalIssuer,
claim.Type,
claim.Value
});
return Json(claims);
}
}
Here's my Startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
//services.AddSingleton<WeatherForecastService>();
services.AddDbContext<Context>(options => options.UseSqlServer(Configuration.GetConnectionString("Context")));
services.AddIdentity<User, Role>().AddEntityFrameworkStores<Context>();
services.AddHttpContextAccessor();
services.AddScoped<IReservationService, ReservationService>();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.LoginPath = "/login/google-login";
})
.AddGoogle(options =>
{
options.ClientId = Configuration["Google:ClientID"];
options.ClientSecret = Configuration["Google:ClientSecret"];
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/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.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
}
If you need any additional info or code, I'll be happy to provide that ASAP.
This line, that is arguably a fairly big part of my project, was preventing communication with Google for who knows what reason.
services.AddIdentity<User, Role>().AddEntityFrameworkStores<Context>();
I now basically just went on without UserManager and RoleManager and just wrote manual methods for accessing AspNetUsers etc.
Probably not a real solution but it is what it is.
Related
I am currently working on a project that consists of sub-projects such as WebApp, API, and Client class library. (The project structure is shown below).
Project Solution Structure
Although the project is a web-based project, it uses windows Identity as authentication identity since it is an internal application. I implemented the authorization policy of the WebApp project without any problems by following the steps in the implementation_link.
Now I can control access using DataAnnotation in WebApp (ex. [Authorize(Roles = "Admin"]). If I add Authorization control on the API side, WebApp cannot access this API. This is because of HttpContext.User is null. I found the solution to this problem solution_link. I adapted this solution to the project as below:
ServiceCollectionExtensions.cs in WebApp project:
public static IServiceCollection AddAuraServices(this IServiceCollection serviceCollection, IConfiguration configuration)
{
serviceCollection.AddTransient<IModelDatabaseNamesProvider, StandardCasingModelDatabasesNamesProvider>();
serviceCollection.Configure<RouteOptions>(routeOptions =>
{
routeOptions.ConstraintMap.Add(ModelDatabasesNameConstraint.Name, typeof(ModelDatabasesNameConstraint));
});
serviceCollection.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
serviceCollection.AddScoped<IModelMetadataProvider>(serviceProvider =>
{
var httpContext = serviceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext;
var modelName = httpContext.Request.RouteValues["model-name"].ToString();
return new ModelMetadataProvider(modelName);
});
DateOnlyTypeConverter.AddAttributeToType();
serviceCollection.AddHttpClient<UploadRulesClient>("ServerAPI", (httpClient) =>
{
httpClient.BaseAddress = new Uri(configuration["AuraApiClient:BaseAddress"]);
}).AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
serviceCollection.AddHttpClient<ScenarioZipFilesClient>("ServerAPI",(httpClient) =>
{
httpClient.BaseAddress = new Uri(configuration["AuraApiClient:BaseAddress"]);
}).AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
serviceCollection.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
.CreateClient("ServerAPI"));
var jsonSerializerOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
ClientJsonResponse.Configure(jsonSerializerOptions);
serviceCollection.AddSingleton(jsonSerializerOptions);
serviceCollection.AddAuraDropzoneConfig(configuration);
return serviceCollection;
}
Startup.cs of WebApp:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(NegotiateDefaults.AuthenticationScheme).AddNegotiate();
services.AddAuthorization();
services.AddControllersWithViews();
//services.AddRazorPages();
services.AddAuraServices(Configuration);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/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.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(name: "model-database", pattern: "{model-name:modeldatabasename}/{controller=ZipFiles}/{action=Index}/{id?}");
endpoints.MapControllerRoute(name: "default", pattern: "", new { controller = "Home", action = "Index" });
//endpoints.MapRazorPages();
});
}
}
But this time I am getting No service for Type Error. How can I solve this problem? Where do you think I am going wrong? Thanks
Edit:
As you can see BaseAddressAuthorizationMessageHandler is in namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication. It is supposed to be used with Blazor WebAssembly apps and it attaches the access token to the authentication header of HttpClient requests. BaseAddressAuthorizationMessageHandler depends on other services like IAccessTokenProvider which is responsible to return the access token. For example in web assembly IAccessTokenProvider default implementation retrieves the access token from browser session storage.
If you want to attach access tokens to your http requests your should probably implement your own DelegatingHandler instead of BaseAddressAuthorizationMessageHandler.
Old answer:
You have to register BaseAddressAuthorizationMessageHandler:
serviceCollection.AddTransient<BaseAddressAuthorizationMessageHandler>();
I have an empty ASP.Net Core application and I'd like to have Azure AD authentication invoked. However my "milldeware" seems always ignore authentication. Please help me to figure out the root cause. Thank you.
My appliaction is in .Net Core 2.2
Below is my startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
services.AddAuthorization(auth =>
{
auth.AddPolicy("AzureAD", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(AzureADDefaults.AuthenticationScheme)
.RequireAuthenticatedUser().Build());
});
services.AddMvc();
services.AddRouting();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseRouter(routes =>
{
routes.MapGet(string.Empty, HomeMiddleware);
routes.MapGet("test", TestMiddleware);
});
}
Below are some middlewares I'd like to routes.
[Authorize]
private Task HomeMiddleware(HttpContext context)
{
return context.Response.WriteAsync($"control, User: {context.User.Identity.Name}");
}
[Authorize]
private Task TestMiddleware(HttpContext context)
{
return Task.Run(() =>
{
var writer = new HttpResponseStreamWriter(context.Response.Body, Encoding.UTF8);
writer.Write("test");
writer.Flush();
writer.Write("another test");
writer.Flush();
});
}
}
Seems [Authorize] doesn't work for my 'middleware'. the context.User.Identity.Name returns nothing to me and it doesn't redirect me to AzureAD authentication page.
Update: net core 2.2 My bad.
I'm not sure the authorizeattribute works out of controllers. But I'd give it a try specifying the name of the policy like:
[Authorize("AzureAD")]
If that works but you don't want to specify the policy every time, I've seen in this link https://learn.microsoft.com/es-es/aspnet/core/security/authorization/secure-data?view=aspnetcore-2.2 there should be a FallbackPolicy property like the DefaultPolicy in 3.1:
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
This is for 3.1.
First, you wan't to setup a default policy not to create just a policy. For that use
auth.AddDefaultPolicy(builder => builder
.AddAuthenticationSchemes(AzureADDefaults.AuthenticationScheme)
.RequireAuthenticatedUser().Build());
Second, you want to add the Authorization middleware, not only the authentication one.
app.UseAuthentication();
app.UseAuthorization();
app.UseRouter(routes =>
...
Without this middleware Authorize attributes have no effect afaik
You can also leave the policy as it is and
A) use it on the app.UseAuthorization() call, I belive there's a overload to choose the default policy name there.
B) leave it being a named not default policy and use [Authorize("<policyName>")] on your endpoints.
my goal is to authenticate the login to a company application through active directory, I currently asked the company's support to create the app registration and install version 3.0.0-rc1.19457.4 in my code (because later It would not let me install them, they said they were not compatible) my project points to netcore3.1 and the web application already had the internal management of login with database, for security this is a new requirement and I never did something similar, so I'm half lost with this.
So, i started reading some guides and posts from here like this: Azure AD Not Authenticating in .NET Core 3.1 including the sample and the git code, but I don't think I have the necessary seniority to understand where the authentication returns, I don't understand if I need to create a view with the name "signin-oid" nor do I understand how to configure the / secret of the home controller.
The debugging and testing process is complicated because the application needs publish and pull request for every change I make, and I can't test this on localhost.
This is my current configuration in Azure, I think it's fine, since almost all the guides said to do this.
Here below I leave the code of both the startup and the controller I want to go to and the appsettings.json
STARTUP
namespace name*
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
var config = new ConfigurationBuilder() //newForAD
.SetBasePath(System.IO.Directory.GetCurrentDirectory())//newForAD
.AddJsonFile("appsettings.json", false)//newForAD
.Build();//newForAD
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)//newForAD
.AddAzureAD(options => config.Bind("AzureAd", options));//newForAD
services.AddControllersWithViews();//newForAD
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting(); // UseRouting must come before UseAuthentication //newForAD
app.UseAuthentication(); // UseAuthentication must come before UseAuthorization //newForAD
app.UseAuthorization(); //newForAD
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
Appsettings.json :
"AzureAd": {
"Instance": "https://login.microsoftonline.com",
"Domain": "https://*****.azurewebsites.net/",
"TenantId": "****",
"ClientId": "*****",
"CallbackPath": "/signin-oidc"
}
Then, the Home Controller:
{
[CustomActionFilter]
public class HomeController : Controller
{
private readonly IHomeApplication _home;
private readonly IWebHostEnvironment _environment;
public HomeController(IHomeApplication home, IWebHostEnvironment environment)
{
_home = home;
_environment = environment;
}
[Route("/")]
public IActionResult Index()
{
return Ok("Home page");
}
[Authorize]
[Route("/secret")]
public IActionResult Secret()
{
var identity = ((ClaimsIdentity)HttpContext.User.Identity);
var name = identity.Claims.FirstOrDefault(c => c.Type == "name")?.Value;
var email = identity.Claims.FirstOrDefault(c => c.Type == "email")?.Value;
return new OkObjectResult(new { name, email });
}}}
If someone can help me to make this work it will solve my week
As junnas said, change your domain to mytenant.onmicrosoft.com and it will work well.
I don't understand if I need to create a view with the name "signin-oidc" nor do I understand how to configure the / secret of the home controller.
You need to ensure your claim contain name and email, otherwise it will get error. You can simple set the Authorize attribute on Index and if you can login, it means that you have configure right.
For more details you could refer to this article and sample here.
I had developed a chat app that uses socket.io for communication between client and server, but I ran into serious problems when trying to scale across more than one node. So I decided to give SignalR a go...
I am using Auth0 for my authentication and had previously developed a library to allow Auth0 to integrate with socket.io. I am now trying to understand how I can integrate Auth0 with SignalR and my React front end.
It is going well, I have managed to get the app authenticating over the socket/HTTP handshake, but I am struggling to understand how to obtain the sub and other information from the decoded JWT, since this sub uniquely identifies my user to the application in general, but to the SignalR hubs in particular.
The difficulty is that the authentication is happening over HTTP, and so the hub is unavailable at that point (or is it? Told you I didn't know what I was talking about...)
I am completely new to ASP.NET Core, and SignalR, so I think I've done quite well! But can someone help me out with obtaining the JWT within my hubs? Here is my startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin",
builder =>
{
builder
.WithOrigins("http://localhost:3010", "http://localhost:3000")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
});
});
var domain = $"https://{Configuration["Auth0:Domain"]}/";
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = domain;
options.Audience = Configuration["Auth0:Audience"];
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/chathub")))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
services.AddAuthorization(options =>
{
options.AddPolicy("read:messages",
policy => policy.Requirements.Add(new HasScopeRequirement("read:messages", domain)));
});
services.AddControllers();
services.AddSignalR();
// Register the scope authorization handler
services.AddSingleton<IAuthorizationHandler, HasScopeHandler>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors("AllowSpecificOrigin");
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<ChatHub>("/chathub");
});
}
}
and here is my hub, such as it is:
namespace WebAPIApplication.Hubs
{
[Authorize]
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
// How do I access the JWT sub here?
await Clients.All.SendAsync("ReceiveMessage", "Some message");
}
}
}
Please help!
Using ASP.NET Identity in my web application, I force new users to enable 2fa after registration.
The issue: the first time a user registers with 2fa (scans the QR code and enters the code) a 404 is thrown. The second time or any number of times after that, the request to server is healthy and they are redirected to the website.
After much digging with middleware and http requests I realised that the first request had an essential cookie expiring 01/01/1970 but any requests made after the first one on the same page had different cookies (which are accepted). I have absolutely no idea why this is, my cookies are registered in StartUp.cs and I haven't this issue anywhere else.
Fiddler Analysis
I would just like to add that before the request is sent, the Cookies look like the second request, but they are somehow reset to what you can see below which causes the request to fail.
404 Bad Request, URL ===> /Identity/Account/Prompt2FA (first time request)
302 Found, URL ===> /Identity/Account/Prompt2FA (second time request)
Any help on this is much appreciated, if you require any code to help resolve this, please do let me know. I wanted to avoid having a very lengthy question. Thanks!
StartUp.cs
public class Startup
{
public IConfiguration Configuration { get; }
public IContainer ApplicationContainer { get; private set; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddIdentity<ApplicationUser, IdentityRole>().AddDefaultTokenProviders().AddEntityFrameworkStores<ApplicationDbContext>();
services.ConfigureApplicationCookie(options =>
{
options.SlidingExpiration = true;
options.ReturnUrlParameter = "/Account/Login";
options.ExpireTimeSpan = TimeSpan.FromHours(2);
options.Cookie.Expiration = TimeSpan.FromHours(2);
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddAntiforgery(option =>
{
option.HeaderName = "XSRF-TOKEN";
option.SuppressXFrameOptionsHeader = false;
});
var containerBuilder = new ContainerBuilder();
containerBuilder.Populate(services);
this.ApplicationContainer = containerBuilder.Build();
var serviceProvider = new AutofacServiceProvider(this.ApplicationContainer);
return serviceProvider;
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.ConfigureCustomExceptionMiddleware();
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc();
}
}
}
So I realised that in StartUp.cs I set
services.Configure<SecurityStampValidatorOptions>(options =>
{
// enables immediate logout, after updating the user's stat, default is 30minutes.
options.ValidationInterval = TimeSpan.Zero;
});
The purpose for this was so that when an Administrator changed a user information (I built a basic Admin CMS). They would be forced to login again as their session would be invalid.
The problem stated in my question arises when a user verifies their Two factor authentication because it changes a field in the database table which then invalidates the users session