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();
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 want to access the values of the Configurations Variables in another class.
Here is the appsetting.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Configurations": {
"FirstVerificationValidationDurationMinutes": 2,
"ResendedVerificationValidationDurationMinutes": 2,
"JwtRegisterTokenValidationDurationMinutes": 30,
"JwtLoginTokenValidationDurationMinutes": 30
}
}
And Here I want to use JwtLoginTokenValidationDurationMinutes's value (which is declared inside appsettings.json as you can see ablove) inside the class below:
public async Task<UserCredentialDto> JwtAuthentication(UserCredentialViewModel userCredential)
{
var user = await _userService.LoginUser(userCredential);
if (user is null)
{
return new UserCredentialDto
{
Status = new StatusMaker().ErrorStatus(user.Status.Message)
};
}
//TODO
var _key = "This is for test";
var tokenHandler = new JwtSecurityTokenHandler();
var tokenKey = Encoding.ASCII.GetBytes(_key);
//var tokenValidationDuration = HERE I NEED THE VALUE //HOW TO ACCESS VALUES FROM appsettings.json?
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddMinutes(/*tokenValidationDuration*/),
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(tokenKey),
SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return new UserCredentialDto
{
Token = tokenHandler.WriteToken(token)
};
}
}
So how can I access values from appsettings.json ?
I tried using Microsoft.Extensions.Configuration but it seems it doesn't exist in .Net 6.
var JwtLoginTokenValidationDurationMinutes = builder.Configuration.GetSection("Configurations:JwtLoginTokenValidationDurationMinutes").Value;
I had identityProviderService working with my API and web app, then I updated i updated my dependecies(identityServer4) and that resulted constantly in the following error either I use code or I hybrid.
IdentityServer4 Error: Invalid_scope
I checked the log and the scope was fine but I can resolve why I keep getting this error! The details as follows:
My local Idp (Identityserver4) log and the main error =>
IdentityServer4.Hosting.IdentityServerMiddleware: Information:
Invoking IdentityServer endpoint:
IdentityServer4.Endpoints.AuthorizeEndpoint for /connect/authorize
IdentityServer4.Validation.DefaultResourceValidator: Error: Scope
Bd.Web.Api not found in store.
IdentityServer4.Endpoints.AuthorizeEndpoint: Error: Request validation
failed IdentityServer4.Endpoints.AuthorizeEndpoint: Information: {
"ClientId": "BdWebAppClientId", "ClientName": "Bd web Client
Application", "RedirectUri": "https://localhost:xxxxx/signin-oidc",
"AllowedRedirectUris": [
"https://localhost:44386/signin-oidc" ], "SubjectId": "anonymous", "ResponseType": "code", "ResponseMode": "form_post",
"GrantType": "authorization_code", "RequestedScopes": "openid
profile Bd.Web.Api", "State":
"CfDJ8Foioa24zShFmzIFmkTbGBTrbiiEwQYmHGMRUN7FwsfKMgY2-olWJA1XVlAkA0uCWIR6HhMdu2X1exzVKTNFTcAD456Z0r3es5ki377uBEJgjA9jmyQFWWzTZV6_7GEmIC39xUh_b_YAqXgtzO0olZ52beNFuxruk_NshL47NhwcaETCH2cy3XTvRN0NTxZHmxVWglo13iSE7RVpNghHc7pBW7jCv7cB2RldQnEvsJ4s56AdiICw9sdKEJ5mQNoXngshanycX4MmD3qaW0TX6knY43pAqMuPgEEVqd7BXKt_koQfiQuAP3pQNbsyOAb1jtoZ8egUHiKgXjofs8ci2i4",
"PromptMode": "", "Raw": {
"client_id": "BdWebAppClientId",
"redirect_uri": "https://localhost:xxxxx/signin-oidc",
"response_type": "code",
"scope": "openid profile Bd.Web.Api",
"response_mode": "form_post",
"nonce": "637284460180108591.ZjIxYjhlZGEtYjk0Mi00M2UxLWExNWItOGYzMjhjODEyMGQzZmU5NjZmZDAtOTQwYi00YTFlLWJlMWUtM2U3YzBhM2NmNjQ4",
"state": "CfDJ8Foioa24zShFmzIFmkTbGBTrbiiEwQYmHGMRUN7FwsfKMgY2-olWJA1XVlAkA0uCWIR6HhMdu2X1exzVKTNFTcAD456Z0r3es5ki377uBEJgjA9jmyQFWWzTZV6_7GEmIC39xUh_b_YAqXgtzO0olZ52beNFuxruk_NshL47NhwcaETCH2cy3XTvRN0NTxZHmxVWglo13iSE7RVpNghHc7pBW7jCv7cB2RldQnEvsJ4s56AdiICw9sdKEJ5mQNoXngshanycX4MmD3qaW0TX6knY43pAqMuPgEEVqd7BXKt_koQfiQuAP3pQNbsyOAb1jtoZ8egUHiKgXjofs8ci2i4",
"x-client-SKU": "ID_NETSTANDARD2_0",
"x-client-ver": "5.5.0.0" }
My config comprises of three classes User, Resources and Client as follows:
public class Users
{
public static List<TestUser> Get()
{
return new List<TestUser> {
new TestUser {
SubjectId = "5BE86359-073C-434B-AD2D-A3932222DABE",
Username = "scott",
Password = "password",
Claims = new List<Claim> {
new Claim(JwtClaimTypes.Email, "scott#scottbrady91.com"),
new Claim(JwtClaimTypes.Role, "admin")
}
}
};
}
}
public class Resources
{
public static IEnumerable<IdentityResource> Ids()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Address()
};
}
public static IEnumerable<ApiResource> Apis()
{
return new List<ApiResource>
{
new ApiResource("Bd.Web.Api", "Bd Web Api") {
Description = "BD API Access",
//UserClaims = new List<string> {"role"},
ApiSecrets = new List<Secret> {new Secret("secret".Sha256())}
}
};
}
}
public class Clients
{
public static IEnumerable<Client> Get()
{
return new List<Client> {
new Client {
ClientId = "BdWebAppClientId",
ClientName = "Bd web Client Application",
AllowedGrantTypes = GrantTypes.Code,
RequirePkce = false,
ClientSecrets = new List<Secret> {
new Secret("secret".Sha256())},
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Address,
"Bd.Web.Api"
},
RedirectUris = new List<string>{"https://localhost:44386/signin-oidc"},
PostLogoutRedirectUris = new List<string>{ "https://localhost:44386/sigout-callback-oidc" }
}
};
}
}
public class Startup
{
// 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.AddControllersWithViews();
services.AddIdentityServer()
.AddInMemoryClients(Clients.Get())
.AddInMemoryIdentityResources(Resources.Ids())
.AddInMemoryApiResources(Resources.Apis())
.AddTestUsers(Users.Get())
.AddDeveloperSigningCredential();
}
// 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();
}
app.UseRouting();
app.UseIdentityServer();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
//endpoints.MapGet("/", async context =>
//{
// await context.Response.WriteAsync("Hello World!");
//});
});
}
}
My web app startup as follows:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
}
public IConfiguration Configuration { get; }
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = Microsoft.AspNetCore.Http.SameSiteMode.None;
});
services.AddMvc(options => { options.EnableEndpointRouting = false; }).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
Formatting = Formatting.Indented,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
services.AddHttpContextAccessor();
services.AddTransient<BearerTokenHandler>();
services.AddHttpClient("ApiClient", client =>
{
//client.BaseAddress = new Uri("https://bandapi.azurewebsites.net/api/");
client.BaseAddress = new Uri("https://localhost:44301/api/");
//client.BaseAddress = new Uri("https://192.168.1.25:44301/api/");
client.Timeout = new TimeSpan(0, 0, 30);
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
}).AddHttpMessageHandler<BearerTokenHandler>();
services.AddHttpClient("IdpClient", client =>
{
client.BaseAddress = new Uri("https://localhost:xxxxx/");
client.Timeout = new TimeSpan(0, 0, 30);
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
}).AddHttpMessageHandler<BearerTokenHandler>();
services.AddAuthentication(options =>
{
options.DefaultScheme = "BdWebAppCookies";
options.DefaultChallengeScheme = "oidc";
//options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
//options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie("BdWebAppCookies" /*CookieAuthenticationDefaults.AuthenticationScheme*/, options =>
{
options.AccessDeniedPath = "/Authentication/AccessDenied";
})
.AddOpenIdConnect("oidc" /*OpenIdConnectDefaults.AuthenticationScheme*/, options =>
{
options.Authority = "https://localhost:xxxx/";
//options.RequireHttpsMetadata = true;
options.ClientId = "BdWebAppClientId";
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("address");
options.Scope.Add("Bd.Web.Api");
options.ClientSecret = "secret";
options.ResponseType = "code";
options.UsePkce = false;
options.SignInScheme = "BdWebAppCookies";
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
In IdentityServer4 4.x the handling of api scopes was changed. Now you need to define all available api scopes and provide them to the configuration.
The updated documentation shows how it's done. https://identityserver4.readthedocs.io/en/latest/topics/resources.html#scopes
public static IEnumerable<ApiScope> GetApiScopes()
{
return new List<ApiScope>
{
new ApiScope(name: "read", displayName: "Read your data."),
new ApiScope(name: "write", displayName: "Write your data."),
new ApiScope(name: "delete", displayName: "Delete your data.")
};
}
Then add the scopes to your IdentityServer configuration:
services.AddIdentityServer()
.AddInMemoryApiScopes(Config.GetApiScopes());
Furthermore you need to add the scopes to your already defined ApiResources as described here: https://identityserver4.readthedocs.io/en/latest/topics/resources.html#api-resources
public static readonly IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("invoices", "Invoice API")
{
Scopes = { "invoice.read", "invoice.pay", "manage" }
},
new ApiResource("customers", "Customer API")
{
Scopes = { "customer.read", "customer.contact", "manage" }
}
};
}
I was had the same problem after updating to .NET Core 3.1 and IndentityServer4 V4.0.0. Only way I could get it to work is to remove the scope parameter on the client API request, using Angular 9 with angular-oauth2-oidc V9.2.1
const header = new HttpHeaders({ 'Content-Type': 'application/x-www-form-urlencoded' });
const params = new HttpParams()
.append('username', userName)
.append('password', password)
.append('client_id', this.clientId)
.append('grant_type', 'password');
this.oauthService.issuer = this.baseUrl;
return from(this.oauthService.loadDiscoveryDocument())
.pipe(mergeMap(() => {
return this.http.post(this.oauthService.tokenEndpoint, params, { headers: header });
}));
This is NOT the way to do this, if you remove the scope then none of the profile information will be returned.
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();
});