External Connection With MicrosoftIdentityWebApp And Identity - authentication

I try to login User from Microsoft account into my app.
First : I'm following this info, and that'a working well.
https://learn.microsoft.com/fr-fr/azure/active-directory/develop/web-app-quickstart?pivots=devlang-aspnet-core
But, when I try merge it with the classic Identity Login, on the external Login, this function return null all the time. And the login info on the starup is correct. I think it was a schema problem but I try to change it and it was not.
var info = await _signInManager.GetExternalLoginInfoAsync();
public async Task<IActionResult> OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (remoteError != null)
{
ErrorMessage = $"Error from external provider: {remoteError}";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
var userl = _signInManager.IsSignedIn(User);
//ExternalLoginInfo info = new ExternalLoginInfo(User, "Microsoft", User.Claims.Where(x => x.Type == "http://schemas.microsoft.com/identity/claims/objectidentifier").FirstOrDefault().Value.ToString(), "Microsoft");
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
if (result.Succeeded)
{
_logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider);
return LocalRedirect(returnUrl);
}
if (result.IsLockedOut)
{
return RedirectToPage("./Lockout");
}
else
{
// If the user does not have an account, then ask the user to create an account.
ReturnUrl = returnUrl;
ProviderDisplayName = info.ProviderDisplayName;
if (info.Principal.HasClaim(c => c.Type == ClaimTypes.Email))
{
Input = new InputModel
{
Email = info.Principal.FindFirstValue(ClaimTypes.Email)
};
}
return Page();
}
}
Startup.cs
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString));
builder.Services.AddDbContext<DatabaseContext>(options => options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = false)
.AddRoles<IdentityRole>()
.AddErrorDescriber<FrenchIdentityErrorDescriber>()
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services
.AddControllersWithViews()
.AddRazorRuntimeCompilation()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
options.JsonSerializerOptions.PropertyNamingPolicy = null;
});
builder.Services.AddMvc();
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromHours(8);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
//options.Cookie.Name = "WebSignature.Session";
});
builder.Services.Configure<IdentityOptions>(options =>
{
// Password settings.
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireUppercase = true;
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 1;
// Lockout settings.
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
// User settings.
options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._#+";
options.User.RequireUniqueEmail = false;
});
builder.Services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromHours(12);
options.LoginPath = ApplicationRedirection.PageConnexion;
options.LogoutPath = "/Identity/Account/Logout";
options.AccessDeniedPath = "/Erreurs/403";
options.SlidingExpiration = true;
});
builder.Services
.AddAuthentication()
.AddJwtBearer(options =>
{
var SecretKey = builder.Configuration.GetSection("JwtToken").GetValue<string>("SecretKey") ?? string.Empty;
var Audience = builder.Configuration.GetSection("JwtToken").GetValue<string>("Audience") ?? string.Empty;
var Issuer = builder.Configuration.GetSection("JwtToken").GetValue<string>("Issuer") ?? string.Empty;
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ClockSkew = TimeSpan.Zero,
ValidateAudience = true,
ValidAudience = Audience,
ValidateIssuer = true,
ValidIssuer = Issuer,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey))
};
});
builder.Services.AddAuthentication()
.AddMicrosoftIdentityWebApp(options =>
{
var config = builder.Configuration.GetSection("AzureAd");
options.Instance = config["Instance"];
options.Domain = config["Domain"];
options.ClientId = config["ClientId"];
options.TenantId = config["TenantId"];
options.ClientSecret = config["ClientSecret"];
options.CallbackPath = config["CallbackPath"];
options.RequireHttpsMetadata = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.UsePkce = true;
options.ResponseType = OpenIdConnectResponseType.Code;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true
};
})
.EnableTokenAcquisitionToCallDownstreamApi(builder.Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' '))
.AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();
builder.Services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy(PolicyApplication.Administrateur, policy => policy.RequireAuthenticatedUser().RequireRole(RoleUtilisateur.Administrateur)/*.AddAuthenticationSchemes(IdentityConstants.ApplicationScheme)*/);
options.AddPolicy(PolicyApplication.Utilisateur, policy => policy.RequireAuthenticatedUser().RequireRole(RoleUtilisateur.Utilisateur)/*.AddAuthenticationSchemes(IdentityConstants.ApplicationScheme)*/);
/// Récupère tous les roles de la class
var AllRole = typeof(RoleUtilisateur).GetFields().Select(x => x.GetValue(x.Name)?.ToString() ?? "").ToArray();
options.AddPolicy(PolicyApplication.All, policy => policy.RequireAuthenticatedUser().RequireRole(AllRole)/*.AddAuthenticationSchemes(IdentityConstants.ApplicationScheme)*/);
/// Pour l'API
options.AddPolicy(PolicyApplication.API, policy => policy.RequireAuthenticatedUser().AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme));
});
builder.Services.Configure<GzipCompressionProviderOptions>(o => o.Level = CompressionLevel.Optimal);
builder.Services.AddKendo();
builder.Services.AddAntiforgery(o => o.HeaderName = "XSRF-TOKEN");
builder.Services.AddRazorPages()
.AddMicrosoftIdentityUI();
var app = builder.Build();
var supportedCultures = new[] { new CultureInfo("fr-FR") };
app.UseRequestLocalization(new RequestLocalizationOptions()
{
DefaultRequestCulture = new RequestCulture(new CultureInfo("fr-FR")),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures
});
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
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.UseSwagger();
app.UseSwaggerUI();
app.UseStatusCodePages(context =>
{
...
return Task.CompletedTask;
});
app.UseSession();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
app.Run();
Value Null
I the problem is due to the claims who was empty. But i don't know why.
enter image description here
So what is wrong, any ideas ?

Finally I found.
Simply use that
builder.Services.AddAuthentication()
.AddMicrosoftAccount(options =>
{
var config = builder.Configuration.GetSection("AzureAd");
options.ClientId = config["ClientId"] ?? string.Empty;
options.ClientSecret = config["ClientSecret"] ?? string.Empty;
options.CallbackPath = config["CallbackPath"] ?? string.Empty;
});
than
builder.Services.AddAuthentication()
.AddMicrosoftIdentityWebApp(options =>
{
var config = builder.Configuration.GetSection("AzureAd");
options.Instance = config["Instance"];
options.Domain = config["Domain"];
options.ClientId = config["ClientId"];
options.TenantId = config["TenantId"];
options.ClientSecret = config["ClientSecret"];
options.CallbackPath = config["CallbackPath"];
options.RequireHttpsMetadata = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.UsePkce = true;
options.ResponseType = OpenIdConnectResponseType.Code;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true
};
})
.EnableTokenAcquisitionToCallDownstreamApi(builder.Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' '))
.AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();

Related

User.Identity is not retrieved when JWT auth is enabled

I have first started with a cookie authentication and somehow after also enabling JWT it doesnt let me to retrieve the user by the User.Identity, object
and I found this article which suggested that you can enable both
https://weblog.west-wind.com/posts/2022/Mar/29/Combining-Bearer-Token-and-Cookie-Auth-in-ASPNET
this is my program cs on the Authentication config
builder.Services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = "JWT_OR_COOKIE"/*"Identity.Application"*/;
options.DefaultChallengeScheme = "JWT_OR_COOKIE";
})
.AddCookie("Cookies", options =>
{
options.LoginPath = "/identity/account/login";
options.ExpireTimeSpan = TimeSpan.FromDays(1);
})
.AddJwtBearer("Bearer",options =>
{
options.RequireHttpsMetadata = false;
options.Authority = "/Security/Token/Validate"; // TODO: Update URL
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience=builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(builder.Configuration["Jwt:Key"])),
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/ConnectionsHub")))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
})
.AddPolicyScheme("JWT_OR_COOKIE", "JWT_OR_COOKIE", options =>
{
// runs on each request
options.ForwardDefaultSelector = context =>
{
string authorization = context.Request.Headers[HeaderNames.Authorization];
if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Bearer "))
return "Bearer";
return "Cookies";
};
});
...
app.UseAuthentication();
app.UseAuthorization();

Use JWT token in multiple projects

I have 3 projects JWT.IDP, JWT.API, JWT.MVC.
JWT.IDP - an API project validates user and issues the JWT token.
JWT.API - an API project for my business logic, CURD etc
JWT.MVC - an MVC application for UI.
My intention is to use this token generated in JWT.IDP and call the JWT.API functions from JWT.MVC
The IDP token is working perfectly fine, I can generate the token and my JWT.MVC Login controller is able to receive it. But when I am trying to use this token to access the JWT.API it gives a 500 error (Please see the last function in the below code (GetWeatherData)).
Can someone help, I am not an advanced user, the code written below is taken from several samples. So I am not sure whether it really is the right code.
namespace JWT.MVC.Controllers
{
public class LoginController : Controller
{
public IActionResult DoLogin()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DoLogin([Bind("EmailOrName,Password")] LoginRequestModel loginRequestModel)
{
var apiName = $"https://localhost:44318/api/User/login";
HttpClient httpClient = new HttpClient();
HttpResponseMessage response = await httpClient.PostAsJsonAsync(apiName, loginRequestModel);
var jasonString = await response.Content.ReadAsStreamAsync();
var data = await JsonSerializer.DeserializeAsync<IEnumerable<AccessibleDb>>
(jasonString, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
foreach (var item in data)
{
item.UserName = loginRequestModel.EmailOrName;
}
return View("SelectDatabase" , data);
}
public async Task<IActionResult> PostLogin(string db, string user)
{
TokenRequestModel tokenRequestModel = new TokenRequestModel() { Database = db, UserName = user };
var apiName = $"https://localhost:44318/api/User/tokenonly";
HttpClient httpClient = new HttpClient();
HttpResponseMessage response = await httpClient.PostAsJsonAsync(apiName, tokenRequestModel);
var jasonString = await response.Content.ReadAsStreamAsync();
var data = await JsonSerializer.DeserializeAsync<AuthenticationModel>
(jasonString, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
var stream = data.Token;
var handler = new JwtSecurityTokenHandler();
var jsonToken = handler.ReadToken(stream);
var tokenS = jsonToken as JwtSecurityToken;
var selectedDb = tokenS.Claims.First(claim => claim.Type == "Database").Value;
ViewBag.SelectedDb = selectedDb;
return View(data);
}
public async Task<IActionResult> GetWeatherData(string token)
{
var apiName = $"https://localhost:44338/weatherforecast";
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
HttpResponseMessage response = await httpClient.GetAsync(apiName);
if (!response.IsSuccessStatusCode)
{
ViewBag.Error = response.StatusCode;
return View("Weatherdata");
}
var jasonString = await response.Content.ReadAsStreamAsync();
var data = await JsonSerializer.DeserializeAsync<WeatherForecast>
(jasonString, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
return View("Weatherdata" , data);
}
}
}
Startup class for JWT.MVC is as below
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Audience = "SecureApiUser";
options.Authority = "https://localhost:44318";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
});
}
Startup class for JWT.API is as below
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
//Copy from IS4
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Audience = "SecureApiUser";
options.Authority = "https://localhost:44318";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
});
//End
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "JWT.API", Version = "v1" });
});
}
Startup class for JWT.IDP is as below
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
//Configuration from AppSettings
services.Configure<JwtSettings>(Configuration.GetSection("JWT"));
//User Manager Service
services.AddIdentity<ApplicationUser, IdentityRole>().AddEntityFrameworkStores<IdentityDbContext>();
services.AddScoped<IUserService, UserService>();
//Adding DB Context with MSSQL
services.AddDbContext<IdentityDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("IdentityDbConnectionString"),
b => b.MigrationsAssembly(typeof(IdentityDbContext).Assembly.FullName)));
//Adding Athentication - JWT
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
o.RequireHttpsMetadata = false;
o.SaveToken = false;
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(Convert.ToInt32(Configuration["JWT:DurationInMinutes"])),
ValidIssuer = Configuration["JWT:Issuer"],
ValidAudience = Configuration["JWT:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWT:Key"]))
};
});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "JWT.IDP", Version = "v1" });
});
}
And the JWT Setting is as below
"JWT": {
"key": "C1CF4B7DC4C4175B6618DE4F55CA4",
"Issuer": "http://localhost:44318",
"Audience": "SecureApiUser",
"DurationInMinutes": 60
},
It's quite surprising that no one was able to identify the mistake. I made the following changes and it works perfectly fine now.
The ConfigureServices is like below in both MVC and API projects. No other changes to any other codes.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
var authenticationProviderKey = "IdentityApiKey";
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("C1CF4B7DC4C4175B6618DE4F55CA4"));
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
ValidateIssuer = true,
ValidIssuer = "http://localhost:44318",
ValidateAudience = true,
ValidAudience = "SecureApiUser",
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
RequireExpirationTime = true,
};
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = authenticationProviderKey;
})
.AddJwtBearer(authenticationProviderKey, x =>
{
x.RequireHttpsMetadata = false;
x.TokenValidationParameters = tokenValidationParameters;
});
//services.AddAuthentication(options =>
//{
// options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
// options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
//}).AddJwtBearer(options =>
//{
// options.Authority = "https://localhost:44318"; ;
// options.RequireHttpsMetadata = false;
// options.Audience = "SecureApiUser";
//});
//End
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "JWT.API2", Version = "v1" });
});
}

Why is user.inisrole false after token renew?

#If (User.IsInRole ("Admin")) returns true when I login to the site. However, when I buy a new token with reflesh token, #if (User.IsInRole ("Admin")) returns false. I host the token in the cookie.
Startup.cs
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, opts =>
{
var tokenOptions = Configuration.GetSection("TokenOption").Get<CustomTokenOption>();
opts.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{
ValidIssuer = tokenOptions.Issuer,
ValidAudience = tokenOptions.Audience[0],
IssuerSigningKey = SignService.GetSymmetricSecurityKey(tokenOptions.SecurityKey),
RoleClaimType = ClaimTypes.Role,
ValidateIssuerSigningKey = true,
ValidateAudience = true,
ValidateIssuer = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
opts.SaveToken = true;
opts.Events = new JwtBearerEvents();
opts.Events.OnMessageReceived = context =>
{
if (context.Request.Cookies.ContainsKey("X-Access-Token"))
{
context.Token = context.Request.Cookies["X-Access-Token"];
}
return Task.CompletedTask;
};
})
.AddCookie(options =>
{
options.Cookie.HttpOnly=true;
options.Cookie.SameSite = SameSiteMode.Strict;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.IsEssential = true;
});
Reflesh token
private async Task<JsonConvertModelNoList<TokenDto>> TokenIsValid(HttpResponseMessage response)
{
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
_httpContextAccessor.HttpContext.Response.Cookies.Delete("X-Access-Token");
_httpContextAccessor.HttpContext.Response.Cookies.Delete("X-Refresh-Token");
var refToken = new RefreshTokenDto();
refToken.Token = _httpContextAccessor.HttpContext.Request.Cookies["X-Refresh-Token"];
var refTokenResponse = await _httpClient.PostAsync("https://localhost:44320/api/Auth/CreateTokenByRefreshToken", refToken,
new JsonMediaTypeFormatter()).ConfigureAwait(false);
JsonConvertModelNoList<TokenDto> result = null;
result = JsonConvert.DeserializeObject<JsonConvertModelNoList<TokenDto>>(await refTokenResponse.Content.ReadAsStringAsync());
if (refTokenResponse.IsSuccessStatusCode)
{
if (result.StatusCode == 200)
{
_httpContextAccessor.HttpContext.Response.Cookies.Append("X-Access-Token", result.Data.AccessToken, new CookieOptions() { HttpOnly = true, SameSite = SameSiteMode.Strict });
_httpContextAccessor.HttpContext.Response.Cookies.Append("X-Refresh-Token", result.Data.RefreshToken, new CookieOptions() { HttpOnly = true, SameSite = SameSiteMode.Strict });
return result;
}
else
{
var errorResult = JsonConvert.DeserializeObject<JsonConvertModelError>(result.Error.ToString());
result.Error = errorResult.errors;
return result;
}
}
else
{
var errorResult = JsonConvert.DeserializeObject<JsonConvertModelError>(result.Error.ToString());
result.Error = errorResult.errors;
return result;
}
}
return null;
}
Does anyone have any idea how to go about it? My English is a little bad. Sorry.

Get access_token working calling API on same IS4 webapp

I'm trying to get the this access_token stuff working to do a call to a API which is declared with an Autohorize attrib from a BaseController. I think there is something wrong with my configuration.
Can anybody tell me what I'm doing wrong?
I have attached my Startup.cs for reference.
I'm trying to get a access token to send with the API called in the code below:
var httpClient = _httpClientFactory.CreateClient(BaseController.AUTHORIZATION_SERVICE_CLIENT_NAME);
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
//AccessToken is always null
var accessToken = _httpContextAccessor.HttpContext.GetTokenAsync("access_token").Result;
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var request = new HttpRequestMessage(HttpMethod.Get, "/api/auth/user/?id=" + id);
var response = httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).Result;
if (response.IsSuccessStatusCode)
return response.Content.ReadAsStringAsync().Result as string;
return "-";
My IS4 startup:
Confi
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddMvc().AddDataAnnotationsLocalization();
services.AddRazorPages()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeAreaFolder("Identity", "/Account/Manage");
});
//START IS4
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
options.UserInteraction.LoginUrl = "/Identity/Account/Login";
options.UserInteraction.LogoutUrl = "/Identity/Account/Logout";
options.Authentication = new AuthenticationOptions()
{
CookieLifetime = TimeSpan.FromHours(10), // ID server cookie timeout set to 10 hours
CookieSlidingExpiration = true
};
})
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
options.EnableTokenCleanup = true;
})
.AddAspNetIdentity<User>();
if (env.IsDevelopment())
builder.AddDeveloperSigningCredential();
else
builder.AddSigningCredential(LoadCertificateFromStore());
//END IS4
//START IDENTITY
services.AddIdentity<User, Role>(options =>
{
options.SignIn.RequireConfirmedEmail = true;
})
.AddEntityFrameworkStores<IdentityContext>()
.AddUserStore<CustomUserStore>()
.AddRoleStore<CustomRoleStore>()
.AddDefaultTokenProviders()
.AddClaimsPrincipalFactory<CustomUserClaimsPrincipalFactory>();
services.ConfigureApplicationCookie(options => {
options.LoginPath = Startup.LoginPath;
options.LogoutPath = Startup.LogoutPath;
options.AccessDeniedPath = Startup.AccessDeniedPath;
});
services.AddAuthentication(o =>{})
.AddGoogle("Google", "Google", options =>
{
options.SignInScheme = IdentityConstants.ExternalScheme;
options.ClientId = configuration.GetValue<string>("Google:ClientId");
options.ClientSecret = configuration.GetValue<string>("Google:ClientSecret");
})
.AddOpenIdConnect("azuread", "Azure AD", options => configuration.Bind("AzureAd", options));
services.Configure<OpenIdConnectOptions>("azuread", options =>
{
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProviderForSignOut = context =>
{
context.HandleResponse();
context.Response.Redirect("/Identity/Account/Logout");
return Task.FromResult(0);
}
};
});
services.AddTransient<IClaimsTransformation, ClaimsTransformer>();
//END IDENTITY
//Set named HttpClient settings for API to get roles of user
services.AddHttpContextAccessor();
services.AddTransient<BearerTokenHandler>();
services.AddHttpClient(BaseController.AUTHORIZATION_SERVICE_CLIENT_NAME, client =>
{
client.BaseAddress = new Uri("https://localhost:44318/");
}).AddHttpMessageHandler<BearerTokenHandler>();
}
public void Configure(IApplicationBuilder app)
{
if (Environment.IsDevelopment())
{
Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler(ErrorPath);
}
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(name: "defaultArea",
pattern: "{area=Identity}/{controller=Account}/{action=Login}/{id?}");
endpoints.MapControllerRoute(name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
}
Bearer token handler, when I add 'AddHttpMessageHandler<BearerTokenHandler' to the client it gives null at the 'expiresAt' variable;
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var accessToken = await GetAccessTokenAsync();
if (!string.IsNullOrWhiteSpace(accessToken))
request.SetBearerToken(accessToken);
return await base.SendAsync(request, cancellationToken);
}
public async Task<string> GetAccessTokenAsync()
{
// get the expires_at value & parse it
var expiresAt = await _httpContextAccessor.HttpContext.GetTokenAsync("expires_at");
var expiresAtAsDateTimeOffset = DateTimeOffset.Parse(expiresAt, CultureInfo.InvariantCulture);
if ((expiresAtAsDateTimeOffset.AddSeconds(-60)).ToUniversalTime() > DateTime.UtcNow)
return await _httpContextAccessor.HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken); // no need to refresh, return the access token
var idpClient = _httpClientFactory.CreateClient("IDPClient");
// get the discovery document
var discoveryReponse = await idpClient.GetDiscoveryDocumentAsync();
// refresh the tokens
var refreshToken = await _httpContextAccessor.HttpContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);
var refreshResponse = await idpClient.RequestRefreshTokenAsync(new RefreshTokenRequest {
Address = discoveryReponse.TokenEndpoint,
ClientId = "mvc",
ClientSecret = "secret",
RefreshToken = refreshToken
});
// store the tokens
var updatedTokens = new List<AuthenticationToken>();
updatedTokens.Add(new AuthenticationToken {
Name = OpenIdConnectParameterNames.IdToken,
Value = refreshResponse.IdentityToken
});
updatedTokens.Add(new AuthenticationToken {
Name = OpenIdConnectParameterNames.AccessToken,
Value = refreshResponse.AccessToken
});
updatedTokens.Add(new AuthenticationToken {
Name = OpenIdConnectParameterNames.RefreshToken,
Value = refreshResponse.RefreshToken
});
updatedTokens.Add(new AuthenticationToken {
Name = "expires_at",
Value = (DateTime.UtcNow + TimeSpan.FromSeconds(refreshResponse.ExpiresIn)).
ToString("o", CultureInfo.InvariantCulture)
});
// get authenticate result, containing the current principal & properties
var currentAuthenticateResult = await _httpContextAccessor.HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
// store the updated tokens
currentAuthenticateResult.Properties.StoreTokens(updatedTokens);
// sign in
await _httpContextAccessor.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, currentAuthenticateResult.Principal, currentAuthenticateResult.Properties);
return refreshResponse.AccessToken;
}
One idea could be to try to set the SaveTokens=true in your configuration.
See this article or ideas.

HttpContext.User null with authenticated user?

So I'm working on a .net core 2 project which where we want to create a basic platform which we can use for our future projects. For the login we use Identity. We have it all setup, the user can succesfully login and the cookie gets set. For some reason once we call HttpContext.User this results in a null. I'm pretty sure it does find an identity, yet this identity is empty. We have checked the cookie and it is perfectly fine, it has it's token. We did add token authentication, but that should not interfere with the cookie system when it sets the cookie.
Below is the Startup.cs
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.AddDbContext<MyIdentityDbContext>(options => options
.UseSqlServer("Data Source=PATH;Initial Catalog=DB;Persist Security Info=True;User ID=ID;Password=*******"));
services.AddSingleton<IJwtFactory, JwtFactory>();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie();
services.AddIdentity<User, IdentityRole>(options =>
{
options.SignIn.RequireConfirmedEmail = true;
options.User.RequireUniqueEmail = false;
options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider;
})
.AddEntityFrameworkStores<MyIdentityDbContext>()
.AddDefaultTokenProviders();
services.Configure<IISOptions>(options =>
{
options.ForwardClientCertificate = false;
});
var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions));
services.Configure<JwtIssuerOptions>(options =>
{
options.Issuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
options.Audience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)];
options.SigningCredentials = new SigningCredentials(_signingKey, SecurityAlgorithms.HmacSha256);
});
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)],
ValidateAudience = true,
ValidAudience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)],
ValidateIssuerSigningKey = true,
IssuerSigningKey = _signingKey,
RequireExpirationTime = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(cfg =>
{
cfg.ClaimsIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
cfg.TokenValidationParameters = tokenValidationParameters;
cfg.SaveToken = true;
});
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = true;
options.Password.RequiredUniqueChars = 6;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
// User settings
options.User.RequireUniqueEmail = true;
});
services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.LoginPath = "/Account/Login"; // If the LoginPath is not set here, ASP.NET Core will default to /Account/Login
options.LogoutPath = "/Account/Logout"; // If the LogoutPath is not set here, ASP.NET Core will default to /Account/Logout
options.AccessDeniedPath = "/Account/AccessDenied"; // If the AccessDeniedPath is not set here, ASP.NET Core will default to /Account/AccessDenied
options.SlidingExpiration = true;
options.Cookie = new CookieBuilder
{
HttpOnly = true,
Name = "MyAuthToken",
Path = "/",
SameSite = SameSiteMode.Lax,
SecurePolicy = CookieSecurePolicy.SameAsRequest
};
});
services.AddAuthorization(options =>
{
options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("Employee"));
options.AddPolicy("OwnerOnly", policy => policy.RequireClaim("Owner"));
options.AddPolicy("AdminOnly", policy => policy.RequireClaim("Admin"));
options.AddPolicy("ModeratorOnly", policy => policy.RequireClaim("Moderator"));
});
services.AddTransient<IEmailSender, EmailSender>();
services.Configure<AuthMessageSenderOptions>(Configuration);
services.AddMvc().AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());
}
Here is the code used in the controller to get the user:
User _user = await _userManager.GetUserAsync(HttpContext.User);
And the code we use to login the user:
var result = await _signInManager.PasswordSignInAsync(model.Email,
model.Password, true, false);
You wrote AddAuthentication two times, once for Cookie and once for JWT and override the defaults.
only use AddAuthentication once and add Cookie and JWT to it.
services.AddAuthentication(options =>
{
// set schema here
})
.AddCookie(config =>
{
//config cookie
})
.AddJwtBearer(config =>
{
//config jwt
});
Now that you have two authentication scheme, you must select which one you want to authenticate your request with
[Authorize(CookieAuthenticationDefaults.AuthenticationScheme)]
or
[Authorize(JwtBearerDefaults.AuthenticationScheme)]
or even both
[Authorize($"{CookieAuthenticationDefaults.AuthenticationScheme},{JwtBearerDefaults.AuthenticationScheme}")]