HttpContext is null when send a request from axios - c#

I am trying to fetch user from my backend, when I am send a request to my controller from backend them HttpContext.User is coming with data. But when I am trying to fetch user from frontend with axios, HttpContext.User is null and data is not returning.
Here is my get User method in Controller:
[HttpGet("me")]
public IActionResult GetMe()
{
var claims = HttpContext.User.Claims.Select(c => new { c.Type, c.Value }).ToList();
return Ok(claims);
}
Here is my authentication configuration in Program.cs:
builder.Services.AddAuthentication("cookie").AddCookie("cookie").AddOAuth("Discord", options =>
{
options.SignInScheme = "cookie";
options.ClientId = builder.Configuration.GetValue<string>("Discord:ClientId");
options.ClientSecret = builder.Configuration.GetValue<string>("Discord:ClientSecret");
options.AuthorizationEndpoint = "https://discord.com/oauth2/authorize";
options.TokenEndpoint = "https://discord.com/api/oauth2/token";
options.CallbackPath = "/oauth/discord-cb";
options.Scope.Add("identify");
options.SaveTokens = true;
options.UserInformationEndpoint = "https://discord.com/api/users/#me";
options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id");
options.ClaimActions.MapJsonKey(ClaimTypes.Name, "username");
options.Events.OnCreatingTicket = async ctx =>
{
var request = new HttpRequestMessage(HttpMethod.Get, ctx.Options.UserInformationEndpoint);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", ctx.AccessToken);
var response = await ctx.Backchannel.SendAsync(request);
var user = await response.Content.ReadFromJsonAsync<JsonElement>();
ctx.RunClaimActions(user);
};
});
app.MapGet("/", (HttpContext ctx) =>
{
ctx.GetTokenAsync("access_token");
return ctx.User.Claims.Select(x => new { x.Type, x.Value }).ToList();
});
app.MapGet("/login", () =>
{
return Results.Challenge(new AuthenticationProperties
{
RedirectUri = "http://localhost:3000"
}, authenticationSchemes: new List<string>() { "Discord" });
});
I also set Cors Policy, and tried this:
private readonly IHttpContextAccessor _httpContextAccessor;
public AuthController(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
[HttpGet("me")]
public IActionResult GetMe()
{
var claims = _httpContextAccessor.HttpContext.User.Claims.Select(c => new { c.Type, c.Value }).ToList();
return Ok(claims);
}
Backend is running on: https://localhost:5005/ (C# Asp.Net Core)
Fronend is running on: http://localhost:3000/ (Vue.js)
When I am go to this url: https://localhost:5005/api/auth/me this is the result:

Related

Reloading page affecting sign out of user/log out

I have been trying to getting this fixed for many times already, but have no success so far. I am logging into my page and everything works fine. However every time I am reloading my page I am ending up signed out and redirected to "Login page" and have to sign in again. What is the problem? Is there is something messed up with my Coockie logic?
I have also tried to implement login.RememberMe logic, but it does not work either. I have checked that login.RememberMe returns true, but no effect.
Login method in controller:
[HttpPost]
public async Task<IActionResult> Login([FromBody] LoginModel login)
{
ApplicationUser user = await this.SignInManager.UserManager.FindByEmailAsync(login.Email);
Microsoft.AspNetCore.Identity.SignInResult result =
await this.SignInManager.PasswordSignInAsync(login.Email, login.Password, login.RememberMe, false);
if (!result.Succeeded)
{
List<string> errors = new List<string>();
errors.Add("Email and password are invalid.");
return BadRequest(new LoginResult
{
Successful = false,
Errors = errors,
});
}
IList<string> roles = await this.SignInManager.UserManager.GetRolesAsync(user);
List<Claim> claims = new List<Claim>
{
new Claim(ClaimTypes.Name, login.Email)
};
ClaimsIdentity identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
ClaimsPrincipal principal = new ClaimsPrincipal(identity);
AuthenticationProperties props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddMonths(1)
};
// to register the cookie to the browser
this.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, props).Wait();
foreach (string role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(this.Configuration["JwtSecurityKey"]));
SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
DateTime expiry = DateTime.Now.AddDays(Convert.ToInt32(this.Configuration["JwtExpiryInDays"]));
JwtSecurityToken token = new JwtSecurityToken(
this.Configuration["JwtIssuer"],
this.Configuration["JwtAudience"],
claims,
expires: expiry,
signingCredentials: creds
);
return Ok(new LoginResult
{
Successful = true,
Token = new JwtSecurityTokenHandler().WriteToken(token),
});
}
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["JwtIssuer"],
ValidAudience = Configuration["JwtAudience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtSecurityKey"]))
};
})
.AddCookie(options =>
{
options.Cookie.Name = "DashCoockie";
options.LoginPath = "/login";
options.ExpireTimeSpan = TimeSpan.FromDays(30);
options.SlidingExpiration = true;
options.EventsType = typeof(CookieAuthEvent);
});
services.AddScoped<CookieAuthEvent>();
services.AddAuthorization(config =>
{
config.AddPolicy(Policies.IsAdmin, Policies.IsAdminPolicy());
config.AddPolicy(Policies.IsUser, Policies.IsUserPolicy());
});
services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = true;
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
});
services.AddControllers();
// Instant update on runtime for development purposes
services.AddControllersWithViews().AddRazorRuntimeCompilation();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseAuthentication();
app.UseAuthorization();
app.UseCookiePolicy();
}
CookieAuthEvent.cs:
public class CookieAuthEvent : CookieAuthenticationEvents
{
public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)
{
context.Request.HttpContext.Items.Add("ExpiresUTC", context.Properties.ExpiresUtc);
}
}
After logging in (Coockie name is the same, just hided first part as it is in the code above):
My project structure is as follows: Client, Server, Shared. I found solution by using LocalStorage and AuthService : IAuthService on Client side. So is it so that in my setup the only solution is to use LocalStorage?
public class AuthService : IAuthService
{
private readonly HttpClient _httpClient;
private readonly AuthenticationStateProvider _authenticationStateProvider;
private readonly ILocalStorageService _localStorage;
public AuthService(HttpClient httpClient,
AuthenticationStateProvider authenticationStateProvider,
ILocalStorageService localStorage)
{
_httpClient = httpClient;
_authenticationStateProvider = authenticationStateProvider;
_localStorage = localStorage;
}
public async Task<RegisterResult> Register(RegisterModel registerModel)
{
var result = await _httpClient.PostJsonAsync<RegisterResult>("api/accounts", registerModel);
return result;
}
public async Task<LoginResult> Login(LoginModel loginModel)
{
var loginAsJson = JsonSerializer.Serialize(loginModel);
var response = await _httpClient.PostAsync("api/Login", new StringContent(loginAsJson, Encoding.UTF8, "application/json"));
var loginResult = JsonSerializer.Deserialize<LoginResult>(await response.Content.ReadAsStringAsync(), new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
if (!response.IsSuccessStatusCode)
{
return loginResult;
}
await _localStorage.SetItemAsync("authToken", loginResult.Token);
((ApiAuthenticationStateProvider)_authenticationStateProvider).MarkUserAsAuthenticated(loginModel.Email);
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", loginResult.Token);
return loginResult;
}
public async Task Logout()
{
await _localStorage.RemoveItemAsync("authToken");
((ApiAuthenticationStateProvider)_authenticationStateProvider).MarkUserAsLoggedOut();
_httpClient.DefaultRequestHeaders.Authorization = null;
}
}
It seems that you didn't set the token in your request header
try add these codes in your startup class:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents()
{
OnMessageReceived = context =>
{
context.Token = context.Request.Cookies["access_token"];
return Task.CompletedTask;
}
};
});
In your Login Action :
Response.Cookies.Append("access_token",your JwtSecurityToken)

How State management of claims work in Blazor Server?

I have implemented Identity Server for Auth Code Flow.
What is correct way to persist the claims (in OnTicketReceived or OnTicketValidated as shown below), so that in subsequent calls
to Blazor pages, I could receive User aka ClaimPrincipal populated for
my use?
Here is code of middleware of my resource server:
public void ConfigureServices(IServiceCollection services)
{
//....
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.SignIn.RequireConfirmedAccount = false;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10);
options.SignIn.RequireConfirmedEmail = false;
})
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<SomeContext>()
.AddDefaultTokenProviders();
Middleware integration:
services.AddAuthentication(options =>
{
options.DefaultScheme = "cookie";
options.DefaultChallengeScheme = "oidc";
options.DefaultSignOutScheme = "oidc";
})
.AddCookie("cookie", options =>
{
options.Cookie.Name = "__Host-bff";
options.Cookie.SameSite = SameSiteMode.Strict;
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:5001";
options.ClientId = "mvc.code";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.ResponseMode = "query";
options.GetClaimsFromUserInfoEndpoint = true;
options.MapInboundClaims = false;
options.SaveTokens = true;
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
//Critical Parts
options.TokenValidationParameters = new()
{
NameClaimType = "name",
RoleClaimType = "role"
};
Authenticated callback from identity server is as following:
options.Events.OnTicketReceived = async n =>
{
var serviceProvider = n.HttpContext.RequestServices;
var accountService = serviceProvider.GetService<IAccountService>() ?? throw new ArgumentNullException("serviceProvider.GetService<IAccountService>()");
I tried using BlazoredSessionStorage etc. but it seems too early to
invoke that. We have to wait until OnPrerender or OnInit
I also tried CustomTokenStore. But how does the claim from cookie
come back to server?
var svc = n.HttpContext.RequestServices.GetRequiredService<IUserAccessTokenStore>();
if (n.Principal != null)
{
var userName = n.Principal.FindFirst(x => x.Type == "name")?.Value;
await accountService.UserCreateAsync(new NewAccount
{
Username = userName,
FirstName = userFirstName,
LastName = userLastName,
//ContactId = 100,
TenantId = 1
});
await (authProvider as SomeAuthenticationStateProvider).LoginAsync(new AuthenticationLogin { Username = userName }, 24 * 60);
}
};
public class CustomTokenStore : IUserAccessTokenStore
{
ConcurrentDictionary<string, UserAccessToken> _tokens = new ConcurrentDictionary<string, UserAccessToken>();
public Task ClearTokenAsync(ClaimsPrincipal user, UserAccessTokenParameters parameters = null)
{
var sub = user.FindFirst("sub").Value;
_tokens.TryRemove(sub, out _);
return Task.CompletedTask;
}
public Task<UserAccessToken> GetTokenAsync(ClaimsPrincipal user, UserAccessTokenParameters parameters = null)
{
var sub = user.FindFirst("sub").Value;
_tokens.TryGetValue(sub, out var value);
return Task.FromResult(value);
}
public Task StoreTokenAsync(ClaimsPrincipal user, string accessToken, DateTimeOffset expiration, string refreshToken = null, UserAccessTokenParameters parameters = null)
{
var sub = user.FindFirst("sub").Value;
var token = new UserAccessToken
{
AccessToken = accessToken,
Expiration = expiration,
RefreshToken = refreshToken
};
_tokens[sub] = token;
return Task.CompletedTask;
}
}
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.ClaimActions.Add(new CustomClaimsFactory(
"userName",
"XXXXX#outlook.com"
));
options.TokenValidationParameters = new TokenValidationParameters
{
// Instead of using the default validation (validating against a single issuer value, as we do in
// line of business apps), we inject our own multitenant validation logic
ValidateIssuer = false,
// If the app is meant to be accessed by entire organizations, add your issuer validation logic here.
//IssuerValidator = (issuer, securityToken, validationParameters) => {
// if (myIssuerValidationLogic(issuer)) return issuer;
//}
};
options.Events = new OpenIdConnectEvents
{
OnTicketReceived = context =>
{
// If your authentication logic is based on users then add your logic here
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
context.Response.Redirect("/Error");
context.HandleResponse(); // Suppress the exception
return Task.CompletedTask;
},
// If your application needs to do authenticate single users, add your user validation below.
//OnTokenValidated = context =>
//{
// var claims = new List<Claim>
// {
// new Claim(ClaimTypes.Role, "superadmin")
// };
// var appIdentity = new ClaimsIdentity(claims);
// context.Principal.AddIdentity(appIdentity);
// return Task.CompletedTask;
// //return myUserValidationLogic(context.Ticket.Principal);
//}
};
});
While CustomActionFactory class is defined below
public class CustomClaimsFactory : ClaimAction
{
string _ClaimType;
string _ValueType;
public CustomClaimsFactory(string claimType, string valueType) : base(claimType, valueType)
{
_ClaimType = claimType;
_ValueType = valueType;
}
public override void Run(JObject userData, ClaimsIdentity identity, string issuer)
{
identity.AddClaim(new Claim(_ClaimType, _ValueType, issuer));
}
}```
OnTokenValidated has a method to add the new identity you create with the claim:
OnTokenValidated = ctx =>{ ctx.Principal.AddIdentity(myNewClaim); }

AddJwtBearer + AddAzureADBearer Not working together

I am writing a authentication piece (.Net Core 2.1) where it can make use of either a JWT from a service provider or a token from AzureAD. I have written the following test to understand what is required , but it seems as if the first authentication provider is always hit when I test with Postman and send through a bearer token.
I have put a break point on both bearer providers events, if i call the values controller with the bearer token it hits the "first" one even though i have specified to use the AuthenticationScheme of "second"
Startup.cs
services.AddAuthentication("first")
.AddJwtBearer("first", options =>
{
options.Authority = "https://123";
options.TokenValidationParameters =
new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = true,
ValidAudiences = new[] { "123" }
};
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var accesToken = context.SecurityToken;
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
var accessToken = context.Principal;
return Task.CompletedTask;
},
};
})
.AddAzureADBearer("second",
AzureADDefaults.JwtBearerAuthenticationScheme,
options => Configuration.Bind("AzureAd", options));
// Added
services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme,
options =>
{
//Configuration.Bind("AzureAd", options);
//options.Authority += "/v2.0";
options.TokenValidationParameters.ValidAudiences = new[] { options.Audience, $"api://{options.Audience}" };
options.TokenValidationParameters.ValidateIssuer = true;
options.IncludeErrorDetails = true;
options.TokenValidationParameters.ValidateLifetime = false;
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var accesToken = context.SecurityToken;
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
var accessToken = context.Principal;
return Task.CompletedTask;
}
};
});
ValuesController.cs
[Route("api/[controller]")]
[ApiController]
[Authorize(AuthenticationSchemes = "second")]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
}
any help will be appreciated.
Try using policies to enable AuthenticationSchemes
We use something like this (didn't try running the code)
services
.AddPolicyScheme("first", "First policy selector", options =>
{
options.ForwardDefaultSelector = context =>
{
return "first"
};
})
.AddPolicyScheme("second", "Second policy selector", options =>
{
options.ForwardDefaultSelector = context =>
{
return "second"
};
})
.AddAuthentication("first")
.AddJwtBearer("first", options =>
{
options.Authority = "https://123";
options.TokenValidationParameters =
new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = true,
ValidAudiences = new[] { "123" }
};
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var accesToken = context.SecurityToken;
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
var accessToken = context.Principal;
return Task.CompletedTask;
},
};
})
.AddAzureADBearer("second",
AzureADDefaults.JwtBearerAuthenticationScheme,
options => Configuration.Bind("AzureAd", options));
// Added
services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme,
options =>
{
//Configuration.Bind("AzureAd", options);
//options.Authority += "/v2.0";
options.TokenValidationParameters.ValidAudiences = new[] { options.Audience, $"api://{options.Audience}" };
options.TokenValidationParameters.ValidateIssuer = true;
options.IncludeErrorDetails = true;
options.TokenValidationParameters.ValidateLifetime = false;
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var accesToken = context.SecurityToken;
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
var accessToken = context.Principal;
return Task.CompletedTask;
}
};
});

JWT Authentication .NET Core 2.2 doesn't update HttpContext

I've searched through some similar questions, but none helped me. I have the following situation:
Basic JWT authentication in .NET Core 2.2, Startup.cs has its settings as:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
var signingConfigurations = new SigningConfigurations();
services.AddSingleton(signingConfigurations);
var tokenConfigurations = new TokenConfigurations();
new ConfigureFromConfigurationOptions<TokenConfigurations>(
Configuration.GetSection("TokenConfigurations"))
.Configure(tokenConfigurations);
services.AddSingleton(tokenConfigurations);
services.AddAuthentication(authOptions =>
{
authOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
authOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(bearerOptions =>
{
var paramsValidation = bearerOptions.TokenValidationParameters;
paramsValidation.IssuerSigningKey = signingConfigurations.Key;
paramsValidation.ValidAudience = tokenConfigurations.Audience;
paramsValidation.ValidIssuer = tokenConfigurations.Issuer;
// Validates a received token signature
paramsValidation.ValidateIssuerSigningKey = true;
// Verifies if a received token is still valid
paramsValidation.ValidateLifetime = true;
// Tolerance time for token expiration (used if there are timezone differences)
paramsValidation.ClockSkew = TimeSpan.Zero;
});
// Activates token usage on this project
services.AddAuthorization(auth =>
{
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser().Build());
});
Then, to generate the token:
LoginController.cs
if (validCredentials)
{
ClaimsIdentity declarations = new ClaimsIdentity(
new GenericIdentity(userInDB.UserName, "Login"),
new[] {
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString("N")),
new Claim(JwtRegisteredClaimNames.UniqueName, userInDB.UserName),
new Claim("userid", userInDB.Id, ClaimValueTypes.String),
new Claim("username", userInDB.UserName, ClaimValueTypes.String),
}
);
DateTime creationDate = DateTime.Now;
DateTime expirationDate = creationDate +
TimeSpan.FromSeconds(tokenConfigurations.Seconds);
var handler = new JwtSecurityTokenHandler();
var securityToken = handler.CreateToken(new SecurityTokenDescriptor
{
Issuer = tokenConfigurations.Issuer,
Audience = tokenConfigurations.Audience,
SigningCredentials = signingConfigurations.SigningCredentials,
Subject = declarations,
NotBefore = creationDate,
Expires = expirationDate
});
var token = handler.WriteToken(securityToken);
return new
{
authenticated = true,
created = creationDate.ToString("yyyy-MM-dd HH:mm:ss"),
expiration = expirationDate.ToString("yyyy-MM-dd HH:mm:ss"),
accessToken = token,
message = "OK"
};
}
All I did so far is based on this walkthrough
So far, so good. If I add [Authorize("Bearer")] on a controller, it can be accessed only if I have the bearer on the header of the request.
But as I was implementing an UserSession structure, to intercept any request, go through the HttpContext and fill the UserSession object, it just doesn't work. The responsible method is this:
ApplicationBuilderExtensions.cs
public static IApplicationBuilder UseSessionConfiguration(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<SessionConfigurationMiddleware>();
}
public class SessionConfigurationMiddleware
{
private readonly RequestDelegate _next;
public SessionConfigurationMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context, IUserSession sessao)
{
if (context.User.Identities.Any(id => id.IsAuthenticated))
{
sessao.UserId = context.User.Claims.FirstOrDefault(x => x.Type == "userid").Value;
sessao.Roles = context.User.Claims.Where(x => x.Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/role").Select(x => x.Value).ToList();
sessao.UserName = context.User.Claims.FirstOrDefault(x => x.Type == "username").Value;
}
// Call the next delegate/middleware in the pipeline
await _next.Invoke(context);
}
}
But, as I debbug this portion of the code (which is correctly being called after any request) the IsAuthenticated property on the HttpContext is always false, in fact, everything is null, or empty on the context.User and I have no idea why.
Am I configuring the jwt wrong?
Is the middleware not correct?
Am I filling the Claims on the jwt correctly?
Why is [Authorize("Bearer")] working correctly if HttpContext is not properly filled?
Are we alone in the universe?
Thanks in advance.

ApiController does not receive Identity info from HttpRequest (Razor)

It is my first time to use ASP.NET core 2.2 Identity to manage Authorization in Controller. In the client side, I use (Login.cshtml.cs) SignInManger.PasswordSignInAsync(…) to sign in a user. After user sign in successfully, I send HttpClient call to UsersController in another project to get more user’s info. However, the Controller is does not receive the the Claims.ClaimsPrincipal (User) information from the Request. The [Authorize(Roles = “admin”)] fails but [AllowAnonymous] passes. I checked the Request cookie. The User HttpContext is not updated to the signed in user in client side. To be exact, I don’t know how to update the User in cookie. I thought it is done in the background. I have looked through many posts but none of them explicitly mentions how the User is being updated in the cookie and send to the controller. It seems that the User updated is done after the program exits the Login.cshtml.cs. I am able to get the Identity of the user by using SingInManager.CreateUserPrincipalAsync(…) within public async Task OnPostAsync(string returnUrl = null) of Login.cshtml.cs. But I don’t know how to pass that Identity to the User in cookie and send it to Controller.
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
// test: get the Identity of the Input.Email
var user = await _signInManager.UserManager.FindByEmailAsync(Input.Email);
var userPrincipal = await _signInManager.CreateUserPrincipalAsync(user);
var identity = userPrincipal.Identity;
// * User Identity does not populate at this point.
var ret = await GetRestOfUserInfo();
}
}
return Page();
}
private async Task<int> GetRestOfUserInfo()
{
var userStatus = await _userManager.GetUserAsync(User);
UserCSDto userCDDto = new UserCSDto()
{
Email = Input.Email,
UserName = Input.Email,
Password = Input.Password,
RememberMe = Input.RememberMe
};
CookieContainer cookieContainer = new CookieContainer();
HttpClientHandler handler = new HttpClientHandler
{
UseCookies = true,
UseDefaultCredentials = true,
CookieContainer = cookieContainer
};
var client = new HttpClient(handler);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var content = new StringContent(JsonConvert.SerializeObject(userCDDto), Encoding.UTF8, "application/json");
var url = _connectionSettings.UrlConnection + "/api/users/authenticate";
var response = await client.PostAsync(url, content);
if (response.IsSuccessStatusCode)
{
var respUser = JsonConvert.DeserializeObject<User>(response.Content.ReadAsStringAsync().Result);
var _responseToken = response.Headers.Where(t => t.Key == "Authorization")
.Select(t => t.Value);
var token = (_responseToken.FirstOrDefault()).FirstOrDefault();
applicationUser.PreferredName = respUser.PreferredName;
applicationUser.Token = token;
ApplicationUser user = await _userManager.FindByEmailAsync(Input.Email);
user.Token = token;
user.PreferredName = respUser.PreferredName ?? "";
IEnumerable<Claim> claims = new Claim[]
{
new Claim("Token", token),
new Claim("PreferredName", "123abc") // respUser.PreferredName)
};
await _userManager.AddClaimsAsync(user, claims);
return 1;
}
return 0;
}
[Authorize]
[ApiController]
[Route("api/[controller]")]
[Produces("application/json")]
public class UsersController : ControllerBase
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private IUserService _userService;
public UsersController(UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
:
:
[Authorize(Roles = "any role")] // failed because User does not contain any info
[AllowAnonymous] // works because no authorization check
[HttpPost("authenticate")]
public async Task<IActionResult> Authenticate([FromBody]UserCSDto userParam)
{
var username = User.Identity.Name; // username is null because User does not
// have info.
var user = _userService.Authenticate(userParam.UserName, userParam.Password);
if (user == null)
return BadRequest(new { message = "Username or passwrod is incorrect" });
var token = _jwtTokenService.CreateToken(userCSDto);
Response.Headers.Add("Authorization", "Bearer " + token);
Response.Headers.Add("Content-Type", "application/json");
return Ok(userCSDto);
}
:
:
}
/////////////////////////////
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConfiguration>(Configuration);
services.Configure<ConnectionSettings>(Configuration.GetSection("ConnectionStrings"));
var connection = Configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<EFCoreContext>(
options => options.UseSqlServer(connection,
b => b.MigrationsAssembly("EFStructures"))); // from Data project
services.AddHttpContextAccessor(); // #.Net Core 2.2
services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = true;
options.Cookie.Expiration = TimeSpan.FromHours(1);
options.SlidingExpiration = true;
});
services.AddSession(options =>
{
options.Cookie.HttpOnly = true;
options.Cookie.Name = "Test.Session";
options.IdleTimeout = TimeSpan.FromMinutes(60);
options.Cookie.Path = "/";
});
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 = true;
// Require Confirmed and Unique Email
options.User.RequireUniqueEmail = true;
options.SignIn.RequireConfirmedEmail = false;
});
#region authenication using JWT token
services.Configure<JwtTokenSettings>(Configuration.GetSection("JwtTokenSettings"));
///// configure jwt authentication
var jwtTokenSettings = Configuration.GetSection("JwtTokenSettings").Get<JwtTokenSettings>();
var key = Encoding.ASCII.GetBytes(jwtTokenSettings.Secret);
//Adds cookie middleware to the services collection and configures it
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options => options.LoginPath = new PathString("/account/login"));
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
//ValidateIssuer = Configuration["AppSettings:Issuer"],
//ValidateAudience = Configuration["AppSettings:Issuer"],
ValidateLifetime = true
};
});
#endregion
#region Add Role services to Identity RAZOR PAGE only
services.AddIdentity<ApplicationUser, IdentityRole>()
// .AddDefaultUI(Microsoft.AspNetCore.Identity.UI.UIFramework.Bootstrap4)
.AddRoles<IdentityRole>()
.AddRoleManager<RoleManager<IdentityRole>>()
.AddDefaultTokenProviders()
.AddEntityFrameworkStores<EFCoreContext>();
#endregion
#region Setting for using Identity
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
#endregion Setting for using Identity
services.AddHttpClient();
services.AddScoped<IJwtTokenService, JwtTokenService>();
services.AddScoped<IEmailSenderService, EmailSenderService>();
services.AddScoped<IEmailSender, EmailSender>();
#region Get EmailSettings from appsettings.json
services.AddOptions();
services.Configure<EmailSettings>(Configuration.GetSection("EmailSettings"));
#endregion Get EmailSettings from appsettings.json
services.AddMvc()
.ConfigureApiBehaviorOptions(options =>
{
options
.SuppressUseValidationProblemDetailsForInvalidModelStateResponses = true;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseAuthentication();
app.UseSession();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRequestLocalization();
app.UseMvcWithDefaultRoute();
// global cors policy
app.UseCors(x => x
.WithOrigins("https://localhost")
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
app.UseMvc();
}
}

Categories