I'm trying out some features of ASP.NET 5 and I'm struggling a bit with authentication. I've managed to use most of this sample app to connect to my Azure AD to log in, but I can't figure out how to restrict parts of my web app to authenticated users only. The article that accompanies the sample app I used states that
You can trigger the middleware to send an OpenID Connect sign-in
request by decorating a class or method with the [Authorize]
attribute, or by issuing a challenge
Since I'd like to avoid repeating the same challenge code everywhere, I opted for the attribute approach, but it doesn't work at all. All it seems to do is block access to unauthorized users, without redirecting to the login page the way the challenge does.
Since I intended the app I am building to be more private than public, I've also tried creating a global policy and opening up some select features using the AllowAnonymous attribute. This works, but again the unauthorized pages are simply shown as blank, instead of a challenge being issued.
This is the policy code I'm using currently, taken from here:
var policy = new AuthorizationPolicyBuilder()
//This is what makes it function like the basic [Authorize] attribute
.RequireAuthenticatedUser()
.Build();
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(new AuthorizeFilter(policy));
});
Am I missing some setup to the authorization attribute or the policy that issues the challenge?
For posterity and most likely my future self as well:
I was missing the AutomaticAuthentication property in the OpenIdConnectOptions. The sample app was set up like this:
// Configure the OWIN Pipeline to use Cookie Authentication
app.UseCookieAuthentication(options =>
{
// By default, all middleware are passive/not automatic. Making cookie middleware automatic so that it acts on all the messages.
options.AutomaticAuthentication = true;
});
// Configure the OWIN Pipeline to use OpenId Connect Authentication
app.UseOpenIdConnectAuthentication(options =>
{
options.ClientId = Configuration.Get("AzureAd:ClientId");
options.Authority = String.Format(Configuration.Get("AzureAd:AadInstance"), Configuration.Get("AzureAd:Tenant"));
options.PostLogoutRedirectUri = Configuration.Get("AzureAd:PostLogoutRedirectUri");
options.Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
};
});
To get everything to work I had to make small adaptations to make it look like this:
app.UseCookieAuthentication(options => { options.AutomaticAuthentication = true; });
// Configure the OWIN Pipeline to use OpenId Connect Authentication
app.UseOpenIdConnectAuthentication(options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.ClientId = Configuration.Get("AzureAd:ClientId");
options.Authority = String.Format(Configuration.Get("AzureAd:AadInstance"), Configuration.Get("AzureAd:Tenant"));
options.PostLogoutRedirectUri = Configuration.Get("AzureAd:PostLogoutRedirectUri");
options.Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
};
options.AutomaticAuthentication = true;
});
Related
I search for similar questions but none responded what I needed so.
In my .net 6 app I can authenticate using normal JwtBearer like
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(configureOptions =>
{
configureOptions.ClaimsIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
configureOptions.TokenValidationParameters = tokenValidationParameters;
configureOptions.SaveToken = true;
});
and I can search my database or external api to decide claims to be included in the token creation process, that starts in a “api/auth/login” endpoint.
It work great with a username and pwd as arguments.
Now I need to add SSO using Azure AD.
I can add authentication easily with
services.AddMicrosoftIdentityWebAppAuthentication(configuration);
and if I do an
app.Use(async (context, next) =>
{
if (context.User.Identity is not null && !context.User.Identity.IsAuthenticated)
{
await context.ChallengeAsync();
}
else
{
await next();
}
});
I can see the username, email and AzureId (not at the same time I do the challenge, but in the next request.. must wait the next() middleware I guess..)
What I want is to use both at the same time and in the token creation process I want to use AzureId and Azure username instead of a username and pwd parameter in the endpoint. If I add the AddMicrosoftIdentityWebAppAuthentication the authentication based on claims don`t work (I would like to keep the default Authorize attribute or replace the default so I can use the minimal api without using [CustomAuthorize(…)], I want to use the fluent way like
app.MapGet("someendpoint", async (IMediator mediator) =>
{
return (await mediator.Send(new SomeRequest { })).Result;
})
.RequireAuthorization("BlaBlaBla");
I don`t mind creating dynamic policies.
I am currently experimenting with external login providers like Google Authentication in my ASP.NET application.
As you can see on my Program.cs i'am running .NET6.
After the Google-Login was successfull, the ClaimsPrincipal has exactly one ClaimsIdentity (IsAuthenticated == true). Now I want to add my own 'Custom-Claim' to this identity. The addition works without any problems, but the next request is missing the custom claim (all other claims by Google are there).
Here is the part of my Program.cs where I add the authentication:
builder.Services.AddAuthentication(x =>
{
x.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme;
})
.AddCookie()
.AddGoogle(GoogleDefaults.AuthenticationScheme, o =>
{
o.ClientId = builder.Configuration["Authentication:Google:ClientId"];
o.ClientSecret = builder.Configuration["Authentication:Google:ClientSecret"];
o.ClaimActions.MapJsonKey("urn:google:picture","picture","url");
});
Here is the configuration of the middleware pipeline in Program.cs:
app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();
app.Use((httpContext, func) =>
{
if (httpContext.Request.Path.StartsWithSegments("/api"))
httpContext.Request.Headers[HeaderNames.XRequestedWith] = "XMLHttpRequest";
return func();
});
app.UseRouting();
app.MapRazorPages();
app.MapControllers();
Endpoint for Google Login:
[HttpGet]
[Route("GoogleSignIn")]
public async Task GoogleSignIn()
{
await HttpContext.ChallengeAsync(GoogleDefaults.AuthenticationScheme,
new AuthenticationProperties {RedirectUri =Url.Action("GoogleResponse")});
}
In the "GoogleResponse" method i add the mentioned custom claim:
//this method gets called after the google login has finished
public async Task<IActionResult> GoogleResponse()
{
var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
var identity = result.Principal.Identities.FirstOrDefault();
var currentSidClaim = identity.FindFirst(x => x.Type == ClaimTypes.Sid);
if (currentSidClaim != null)
identity.RemoveClaim(currentSidClaim);
identity.AddClaim(new Claim(ClaimTypes.Sid, "Hi i am some claim, but i'll miss on the next request :("));
return Redirect("/");
}
ClaimsIdentity after adding the claim:
[https://i.stack.imgur.com/INdGy.png]
ClaimsIdentity on the next request (in the same controller btw):
[https://i.stack.imgur.com/oft3e.png]
Whether I access the identity via "User.Identity" or "HttpContext.User..." makes no difference.
Really hope somebody can clear things up for me.
I hope I don't have to implement a large / elaborate ASP.NET Identity solution to solve the problem. I would be happy with a lightweight authentication.
UPDATE:
After some further tests it looks like it is not necessarily related to the specific Google login. Even if I create a second identity at login, it is gone at the next request.
I guess the authentication cookie is not updated.
Thanks in advance!
Try to add claims after callback from the external website. As described here
Problem: My as an app calls to a downstream web api throw a null exception error after adding my own jwt bearer authentication.
I have a .net 5 web API, call it AppAPI, whose ConfigureServices has the following code:
var accessTokenKey = Convert.FromBase64String(Configuration.GetValue<string>("AccessCodeSecret"));
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer("AccessToken", o =>
{
o.RequireHttpsMetadata = false;
o.SaveToken = true;
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(accessTokenKey),
ValidateIssuer = false,
ValidateAudience = false
};
})
.AddMicrosoftIdentityWebApi(Configuration, "AzureAd", "AzureAd")
.EnableTokenAcquisitionToCallDownstreamApi()
.AddDownstreamWebApi("CommonServicesApi", Configuration.GetSection("CommonServicesApi"))
.AddInMemoryTokenCaches();
//services.AddAuthorization();
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("AccessToken")
.Build();
});
I have an /auth endpoint that accepts an access token from Azure AD, and generates a new access token with my own custom claims based on the database. The controller uses an authorize attribute to ensure it uses the correct mechanism:
[Authorize(AuthenticationSchemes = "AzureAd")]
The default policy from above ensures the rest of the endpoints use this access token for every other endpoint that doesnt specify a scheme.
I have a 2nd web API, called CommonServices, that is only accessible from other APIs, not clients directly. So AppAPI uses AddDownstreamwebapi to handle those calls. This worked previously to me adding my own app access tokens, meaning I only had one auth mechanism - AddMicrosoftIdentityWebApi. I started receiving my error when I added my own JwtBearer auth - "AccessToken".
The controller that has the error injects IDownstreamWebApi commonServicesApi. It uses the default auth scheme of "AccessToken". The code looks like this:
var response = await _commonServicesApi.CallWebApiForAppAsync("CommonServicesApi", "AzureAd",
options => { options.RelativePath = "Projects"});
var json = await response.Content.ReadAsStringAsync();
The 2nd parameter "AzureAd" was my attempt to have the commonservicesApi use the correct scheme. I am not even sure if that's the right scheme to use, or if .EnbleTokenAcquisitionToCallDownstreamApi adds a 3rd scheme that should be specified.
It is this call that I receive
System.NullReferenceException: 'Object reference not set to an instance of an object.'
at Microsoft.Identity.Web.MergedOptions.PrepareAuthorityInstanceForMsal()
This exception was originally thrown at this call stack:
Microsoft.Identity.Web.MergedOptions.PrepareAuthorityInstanceForMsal()
Microsoft.Identity.Web.TokenAcquisition.BuildConfidentialClientApplication(Microsoft.Identity.Web.MergedOptions)
Microsoft.Identity.Web.TokenAcquisition.GetOrBuildConfidentialClientApplication(Microsoft.Identity.Web.MergedOptions)
Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForAppAsync(string, string, string, Microsoft.Identity.Web.TokenAcquisitionOptions)
Microsoft.Identity.Web.DownstreamWebApi.CallWebApiForAppAsync(string, string, System.Action<Microsoft.Identity.Web.DownstreamWebApiOptions>, System.Net.Http.StringContent)
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
I can't seem to figure out what is null, or how to approach solving this problem.
I have an API app created using asp core. I'm trying to enforce use of client certificates as described here.
I did tell Kestrel to require certificates in Program.cs:
webBuilder.ConfigureKestrel(o =>
{
o.ConfigureHttpsDefaults(o => o.ClientCertificateMode = ClientCertificateMode.RequireCertificate);
});
And I did add event handler in Startup.cs:
services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate(options =>
{
options.Events = new CertificateAuthenticationEvents
{
OnCertificateValidated = context =>
{
}
};
});
When I debug the API running locally it still doesn't require any certificates. If I provide certificate anyway, the breakpoint in the event handler is never hit.
I have a ASP.Core web app that uses windows authentication I am trying to setup integration tests for.
inside the startup the authorization is configured as follows
services.Configure<IISOptions>(options =>
{
options.ForwardWindowsAuthentication = true;
});
services.AddAuthorization(options =>
{
options.AddPolicy("SiteRead", policy => policy.RequireAssertion(
context => context.User.HasClaim(
x => x.Value == "groupSidHere"
)));
});
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
The test is as follows
var server = new TestServer(builder);
var client = server.CreateClient();
var response = await client.GetAsync("/");
response.EnsureSuccessStatusCode();
The test fails with the following response
InvalidOperationException: No authentication handler is configured to handle the scheme: Automatic
All the documentation I have been able to find for integration tests doesn't cover this scenario(windows auth). Has anyone found a solution to this?
See this issue, they say:
We ended up solving our need for Windows auth with TestServer by creating a little library that will inject some windows auth services into the pipeline to emulate the behavior provided by IIS - you can find it at
You will find their library "IntelliTect.AspNetCore.TestHost.WindowsAuth" here.
I faced the same issue, and that library worked for me!
And it actually inject real windows authentication data, not just a mock data.