I am currently trying to use 2 different bearer tokens in a .net core 2.2 app. I would like to use an Identity Server token and an Azure AD bearer token. According to Microsoft this is possible (https://learn.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme?view=aspnetcore-2.2) but I am having no success getting it working.
I have the Identity Server token as the "default" authentication followed by the AzureAD token as documented in the aforementioned link:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = true,
ValidateIssuer = true,
ValidateLifetime = true,
ClockSkew = ClockSkew
};
o.Audience = Audience;
o.Authority = IdentityIssuer;
o.RequireHttpsMetadata = true;
})
.AddJwtBearer("AzureAd",o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
};
o.Audience = AudienceUri;
o.Authority = Authority
});
Identity Server tokens validate as expected; however Azure AD tokens do not. They appear to always hit the default Bearer token handler.
Try with something like this (I have 2 auth schemes; one for AAD and another one for custom Bearer auth)
var url = new MongoUrl(mongoSettings.ConnectionString); // I'm using MONGODB as databse ..but you can choose what you want
var client = new MongoClient(url);
var database = client.GetDatabase(url.DatabaseName);
services.AddIdentity<ApplicationUser, ApplicationRole>(options =>
{
options.Password.RequireDigit = true;
options.Password.RequiredLength = 6;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = true;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 0;
// ApplicationUser settings
options.User.RequireUniqueEmail = false;
//options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#.-_";
}).RegisterMongoStores<ApplicationUser, ApplicationRole>(
p => database.GetCollection<ApplicationUser>("AspNetUsers"),
p => database.GetCollection<ApplicationRole>("AspNetRoles"))
.AddDefaultTokenProviders();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); // => remove default claims
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(appConfiguration.Key));
var tokenValidationParameters = new TokenValidationParameters
{
//RequireExpirationTime = true,
//RequireSignedTokens = true,
//ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
ValidateIssuer = false,
ValidIssuer = appConfiguration.SiteUrl,
ValidateAudience = false,
ValidAudience = appConfiguration.SiteUrl,
//ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
services.AddAuthentication(options =>
{
//options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer("AAD", options =>
{
//options.Audience = appConfiguration.SiteUrl;
//options.ClaimsIssuer = appConfiguration.SiteUrl;
options.IncludeErrorDetails = true;
options.Authority = "https://sts.windows.net/800859e2-e8c3-4842-b31a-3b3727070cb6/v2.0";
options.Audience = "5e2ddaf2-2ed3-4829-bbe8-9aa127a754ef";
options.SaveToken = true;
options.Events = new JwtBearerEvents()
{
OnMessageReceived = context =>
{
if ((context.Request.Path.Value.StartsWith("/videohub")
//|| context.Request.Path.Value.StartsWith("/looney")
//|| context.Request.Path.Value.StartsWith("/usersdm")
)
&& context.Request.Query.TryGetValue("token", out StringValues token)
)
{
context.Token = token;
}
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
//TODO:
return Task.FromResult(0);
},
OnTokenValidated = context =>
{
//At this point, the security token has been validated successfully and a ClaimsIdentity has been created
var claimsIdentity = (ClaimsIdentity)context.Principal.Identity;
//get username
var preferred_username = claimsIdentity.Claims.ToList().Where(c => c.Type == "preferred_username").Select(c => c.Value).FirstOrDefault();
var username = !string.IsNullOrEmpty(preferred_username) ? preferred_username : claimsIdentity.Claims.ToList().Where(c => c.Type == "upn").Select(c => c.Value).FirstOrDefault();
//add your custom claims here
var serviceProvider = services.BuildServiceProvider();
var userservice = serviceProvider.GetService<IUsersService>();
var us = userservice.Find(xx => xx.UserName == username);
if (us == null) return Task.FromResult(0);
// ADD SCHEMA (so we know which kind of token is .. from AZURE ACTIVE DIRECTORY .. OR CUSTOM)
// TO RETRIEVE THE SCHEMA ..--> //var result = User.Claims.Where(c=>c.Type=="schema").FirstOrDefault().Value;
claimsIdentity.AddClaim(new Claim("schema", "AAD"));
//GET ROLES FROM DB
if (us != null && us.Roles.Any())
{
//add THEM
us.Roles.ForEach(rr =>
{
claimsIdentity.AddClaim(new Claim(ClaimsIdentity.DefaultRoleClaimType, rr.ToUpper()));
});
}
else
{
//OR ADD A DEFAULT ONE
claimsIdentity.AddClaim(new Claim(ClaimsIdentity.DefaultRoleClaimType, Constant.ROLES.Dipendente));
}
// add MONGDB Id as ClaimTypes.NameIdentifier
claimsIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, us.Id));
return Task.FromResult(0);
}
};
}).AddJwtBearer("CUSTOM", options =>
{
//options.Audience = appConfiguration.SiteUrl;
//options.ClaimsIssuer = appConfiguration.SiteUrl;
options.TokenValidationParameters = tokenValidationParameters;
options.SaveToken = true;
options.Events = new JwtBearerEvents()
{
OnAuthenticationFailed = context =>
{
//TODO:
return Task.FromResult(0);
},
OnTokenValidated = context =>
{
//At this point, the security token has been validated successfully and a ClaimsIdentity has been created
var claimsIdentity = (ClaimsIdentity)context.Principal.Identity;
//add your custom claims here
// ADD SCHEMA (so we know which kind of token is .. from AZURE ACTIVE DIRECOTY .. OR CUSTOM)
claimsIdentity.AddClaim(new Claim("schema", "CUSTOM"));
return Task.FromResult(0);
}
};
});
then in yours Controller mark class or methid as :
[Route("api/[controller]")]
[ApiController]
[Authorize(AuthenticationSchemes = "AAD,CUSTOM")] //<-- yours schema
public class AccountController : Controller
{
// ...
}
Hope it helps you!!
Possible things you could try:
1 Set up the default policy
services.AddAuthorization(options => {
options.DefaultPolicy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme, "AzureAD")
.RequireAuthenticatedUser()
.Build();
2 On the OnAuthenticationFailed > under one of the jwtOptions.Events, add a condition if it's authenticated then complete the task and don't show the error. Sometimes the user is authenticated already but the error from one provider prevents the proper response
if (arg.HttpContext.User.Identity.IsAuthenticated)
{
return Task.CompletedTask;
}
3 If this doesn't work. There's a hack to check if it's authenticated. Add more conditions per scheme.
app.Use(async (context, next) =>
{
if (!context.User.Identity.IsAuthenticated)
{
var result = await context.AuthenticateAsync("AzureAD");
if (result?.Principal != null)
{
context.User = result.Principal;
}
}
await next.Invoke();
});
Related
I converted my application from .NET 5 to .NET 6. In the old version the response from the api was very fast. But in the new version it is very slow. The record has only 2 objects. This is my code. Please help. Thank!
In .NET 5 EF core:
namespace AspNetCoreTemplate.IoC
{
public static class NativeInjectorConfig
{
public static void RegisterServices(this IServiceCollection services)
{
services.AddScoped<ISongService, SongService>();
services.AddScoped<IUnitOfWork, UnitOfWork>();
}
}
}
In .Net 6 EF core:
public class CoreModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterGeneric(typeof(GenericRepository<>)).As(typeof(IGenericRepository<>)).InstancePerLifetimeScope();
builder.RegisterGeneric(typeof(Service<>)).As(typeof(IGenericService<>)).InstancePerLifetimeScope();
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>();
var apiAssembly = Assembly.GetExecutingAssembly();
var repoAssembly = Assembly.GetAssembly(typeof(DatabaseContext));
var serviceAssembly = Assembly.GetAssembly(typeof(MapProfile));
builder.RegisterAssemblyTypes(apiAssembly, repoAssembly, serviceAssembly).Where(x => x.Name.EndsWith("Repository")).AsImplementedInterfaces().InstancePerLifetimeScope();
builder.RegisterAssemblyTypes(apiAssembly, repoAssembly, serviceAssembly).Where(x => x.Name.EndsWith("Service")).AsImplementedInterfaces().InstancePerLifetimeScope();
}
}
My response:
Program.cs
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers(options => options.Filters.Add(new ValidateFilterAttribute())).AddFluentValidation(x => x.RegisterValidatorsFromAssemblyContaining<UserLoginDtoValidator>());
builder.Services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = builder.Configuration["Jwt:Audience"],
ValidIssuer = builder.Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
};
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddScoped(typeof(NotFoundIdFilter<>));
builder.Services.AddScoped(typeof(NotFoundUpdateFilter<>));
builder.Services.AddAutoMapper(typeof(MapProfile));
builder.Services.AddDbContext<DatabaseContext>(x =>
{
x.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"), option =>
{
option.MigrationsAssembly(Assembly.GetAssembly(typeof(DatabaseContext)).GetName().Name);
});
});
builder.Host.UseServiceProviderFactory
(new AutofacServiceProviderFactory());
builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder => containerBuilder.RegisterModule(new CoreModule()));
var app = builder.Build();
Old repo: https://github.com/ThanhDeveloper/AspNetCoreWebApplicationTemplate/blob/faf8296e20/AspNetCoreTemplate/IoC/NativeInjectorConfig.cs
New repo: https://github.com/ThanhDeveloper/AspNetCoreWebApplicationTemplate
I have a dotnet core 5 API with swagger enabled.
I have added authorization in swagger api and works fine token is sent with the request.
But i have a requirement in which i need to send header value along with the request, for that purpose i have created a class:
public class SwaggerHeaderFilter : IOperationFilter {
public void Apply(OpenApiOperation operation, OperationFilterContext context) {
if (operation.Parameters == null)
operation.Parameters = new List<OpenApiParameter>();
var scheme = new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "bearer" } };
operation.Security.Add(new OpenApiSecurityRequirement {
[scheme] = new List<string>()
});
operation.Parameters.Add(new OpenApiParameter {
Name = "channelId",
In = ParameterLocation.Header,
Required = false // set to false if this is optional
});
}
}
And in startup.cs file i have used it like this:
services.AddSwaggerGen(c => {
c.SwaggerDoc("v1", new OpenApiInfo { Title = "API", Version = "v1" });
c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme {
In = ParameterLocation.Header,
Description = "Please enter token",
Name = "Authorization",
Type = SecuritySchemeType.Http,
BearerFormat = "JWT",
Scheme = "bearer"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type=ReferenceType.SecurityScheme,
Id="Bearer"
}
},
new string[]{}
}
});
c.OperationFilter<SwaggerHeaderFilter>();
});
But the problem is when i add custom header i am not getting anything in token
I am currently using ASP.NET Core Identity. I cannot figure out the setting to extend the session length but I keep getting logged out - I assume there's a sliding expiration of ~20 minutes, but I can't find the setting. Note, I am using Google as external OAuth.
services.AddIdentity<ApplicationUser, IdentityRole>(o =>
{
o.Password.RequireDigit = false;
o.Password.RequireLowercase = false;
o.Password.RequireUppercase = false;
o.Password.RequireNonAlphanumeric = false;
o.Password.RequiredLength = 6;
o.SecurityStampValidationInterval = TimeSpan.FromHours(8);
o.Cookies.ExternalCookie.ExpireTimeSpan = TimeSpan.FromHours(8);
o.Cookies.ApplicationCookie.ExpireTimeSpan = TimeSpan.FromHours(8);
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
app.UseIdentityServer();
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = $"http://localhost:55504/",
RequireHttpsMetadata = false,
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"name",
"given_name",
"family_name",
"role"
}
});
var googleOptions = serviceProvider.GetRequiredService<GoogleOptions>();
app.UseGoogleAuthentication(new GoogleOptions
{
AuthenticationScheme = "Google",
SignInScheme = "Identity.External",
ClientId = googleOptions.ClientId,
ClientSecret = googleOptions.ClientSecret
});
This question\answer is specific to Identity Server 4.
You would do something like in your Configure:
app.UseGoogleAuthentication(new GoogleOptions
{
SignInScheme = "Identity.External", // this is the name of the cookie middleware registered by UseIdentity()
ClientId = Configuration["ExternalAuthentication:Google:ClientId"],
ClientSecret = Configuration["ExternalAuthentication:Google:ClientSecret"]
});
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = $"http://localhost:55504/",
RequireHttpsMetadata = false,
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"name",
"given_name",
"family_name",
"role"
}
// CookieLifetime default is 10 Hours
Authentication.CookieLifetime = TimeSpan.FromHours(24);
// Default CookieSlidingExpiration = false;
Authentication.CookieSlidingExpiration = true;
});
and in your ConfigureServices
// Identity
// https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity
// http://docs.identityserver.io/en/release/quickstarts/6_aspnet_identity.html
services.AddIdentity<ApplicationUser, IdentityRole>(o => {
// configure identity options
o.Password.RequireDigit = false;
o.Password.RequireLowercase = false;
o.Password.RequireUppercase = false;
o.Password.RequireNonAlphanumeric = false;
o.Password.RequiredLength = 6;
})
.AddEntityFrameworkStores<AuthDbContext>()
.AddDefaultTokenProviders();
Hi i am trying to figure out how to loop through a request table with a user key that has only reqdetails. I have tried following the docs, but it's not working.
I just need to filter out all the user key that has only reqdetails only. For example user key of OAJ2WNWQPUfwJCpAJ11FWIA8kPn2 has a reqdetails.
Btw i am following this link:
https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot
Here is my firebase console:
Here is my declaration and constructor
request: FirebaseListObservable<any>;
userkey: FirebaseListObservable<any>;
reqdetails: FirebaseListObservable<any>;
userreq: FirebaseListObservable<any>;
constructor(public navCtrl: NavController, public navParams: NavParams, angFire: AngularFireDatabase) {
this.request = angFire.list('/request');
this.userreq = angFire.list(`${this.userkey}`);
this.reqdetails = angFire.list('reqdetails');
}
Here is my OpenMapPage method
openMapPage()
{
let process = this.request.subscribe(records => {
// your logic
records.forEach(record => {
var ref = firebase.database().ref("request");
ref.once("value")
.then(function(snapshot) {
var a = snapshot.exists(); // true
var c = snapshot.hasChild("reqdetails"); // true
var d = snapshot.child('reqdetails').exists();
if (snapshot.hasChild('reqdetails'))
{
console.log(record.$key);
}
});
});
});
this.navCtrl.push(MapPage);
}
You need to specify the key of your target request or loop on all requests.
If you know the key :
ref.child(REQUEST_KEY).once("value", function(snapshot) {
var requestKey = snapshot.key;
var requestValue = snapshot.val();
var reqdetails = requestValue.reqdetails;
console.log(reqdetails);
}
If you want all requests :
ref.once("value", function(snapshot) {
var requestsKey = snapshot.key;
var requestsValue = snapshot.val();
snapshot.forEach(function(childSnapshot) {
var requestKey = childSnapshot.key;
var requestValue = childSnapshot.val();
var reqdetails = requestValue.reqdetails;
console.log(reqdetails);
});
}
In MVC 6 RCP 6 using Microsoft.AspNet.Security I was able to use a custom SecurityTokenValidator.
In RC Microsoft.AspNet.Security didn't exist in Beta4 so I changed my code to use Microsoft.AspNet.Authentication see below: (Compiles and runs but SecurityTokenValidator never fires.
services.Configure<ExternalAuthenticationOptions>(options =>
{
options.SignInScheme = OAuthBearerAuthenticationDefaults.AuthenticationScheme;
});
app.UseOAuthBearerAuthentication(options =>
{
options.TokenValidationParameters.ValidateAudience = true;
options.TokenValidationParameters.ValidateIssuer = true;
options.TokenValidationParameters.RequireSignedTokens = false;
options.AuthenticationScheme = OAuthBearerAuthenticationDefaults.AuthenticationScheme;
options.AutomaticAuthentication = true;
options.SecurityTokenValidators = new List<ISecurityTokenValidator> { validator };
});
Replace the app.UseOAuthBearerAuthentication code with
app.UseMiddleware<OAuthBearerAuthenticationMiddleware>(new ConfigureOptions<OAuthBearerAuthenticationOptions>(options =>
{
options.AutomaticAuthentication = true;
options.SecurityTokenValidators = new List<ISecurityTokenValidator> { validator };
}));
You got it?
Today occours that CustomSecurityValidationToken not fires because inner exception was throw (a inner validation occurs based in params in my case).
Try to debug Notifications and if it fires 'AuthenticationFailed', you will find in 'context' variable a Property named 'Exception' if any.
app.UseOAuthBearerAuthentication(bearer =>
{
bearer.SecurityTokenValidators = new List<ISecurityTokenValidator>() { new CustomSecurityValidationToken() };
bearer.AutomaticAuthentication = true;
bearer.Notifications = new OAuthBearerAuthenticationNotifications()
{
SecurityTokenReceived = context =>
{
return Task.FromResult(0);
},
MessageReceived = context =>
{
return Task.FromResult(0);
},
SecurityTokenValidated = context =>
{
return Task.FromResult(0);
},
AuthenticationFailed = context =>
{
context.Response.Redirect("Home/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
}
};
});