How to Keep Session Even after Browser is Closed for Persistent Cookie - session-cookies

I am using cookie based authentication and making it persistent when login:
var claims = new List<Claim>()
{
new Claim("UserName", user.UserName)
};
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
var utcNow = DateTime.UtcNow;
var props = new AuthenticationProperties()
{
IsPersistent = true,
IssuedUtc = utcNow,
ExpiresUtc = utcNow.AddMinutes(60)
};
_httpcontext.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, props).Wait();
My configuration details:
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
options.LoginPath = "/Account/Login";
options.AccessDeniedPath = "/Global/AccessDenied";
});
services.AddAuthorization(config =>
{
config.AddPolicy(CommonConstValues.AuthorizationPolicyName, policyBuilder =>
{
policyBuilder.UserRequireCustomClaim(CustomClaimTypes.UserName.ToString());
policyBuilder.UserRequireCustomClaim(ClaimTypes.Uri);
});
});
services.AddHttpContextAccessor();
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromSeconds(Convert.ToDouble(configuration["SessionIdleTimeoutSeconds"]));
options.IOTimeout = Timeout.InfiniteTimeSpan;
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
Now after closing and reopening the browser when I browse the application I get the authentication is valid but Session Data is cleared. I can see the Cookie is there but the HttpContext has changed.
How to persist the session too? Did I do anything wrong?

Sessions are inherently not persisted. In fact the use of a server-side session is not a best practice (security, scalability).
If you were to persist the session-cookie, you would also need to store all the data that you will store in the server-side session in a backend store (database, permanent redis-cache, ...) (and provision to clear out abandoned sessions)
So while not answering your question directly, please reconsider your use case for needing to store the session cookie.

Related

How to configure Identity Server with NSwag API using IIdentityServerBuilder's .AddApiAuthorization()?

I'd like to create an authentication/authorization flow of sorts using Identity Server to have a user authorize themselves in my Swagger API so that they may access endpoints marked with the [Authorize] attribute. This is the current flow I have:
I have Swagger set up with the NSwag middleware with the OAuth2 security scheme:
services.AddMvcCore().AddApiExplorer();
services.AddOpenApiDocument(settings =>
{
settings.Title = "MyProject Services";
settings.Version = "1.0";
settings.AddSecurity("oauth2", new NSwag.OpenApiSecurityScheme
{
Type = NSwag.OpenApiSecuritySchemeType.OAuth2,
Flow = NSwag.OpenApiOAuth2Flow.AccessCode,
AuthorizationUrl = "/connect/authorize",
TokenUrl = "/connect/token",
Scopes = new Dictionary<string, string>
{
{ "MyProjectServicesAPI", "API Access" }
}
});
settings.OperationProcessors.Add(new AspNetCoreOperationSecurityScopeProcessor("oauth2"));
});
And the OAuth2 client settings in Configure():
app.UseOpenApi();
app.UseSwaggerUi3(options =>
{
options.OAuth2Client = new NSwag.AspNetCore.OAuth2ClientSettings
{
ClientId = "MyProjectAPI",
ClientSecret = "mysecret",
UsePkceWithAuthorizationCodeGrant = true
};
});
After a user selects the scope and authorizes, they get redirected to my Identity Server Login Page I scaffolded and from there they can login. Once they put in their credentials and press, 'Login', they then get redirected back to the Swagger API. So far so good. Now this is where I start to have trouble cause I would like to later add policies so a user must have a specific claim to access an endpoint, but right now, I'm not able to see any of my user's claims in the JWT Bearer token that's in the request header when I access and endpoint. The only information I get about my user is in the 'sub' which is their GUID. I'd like to be able to get their username, email, and role(s) as well.
This is what I have setup for Identity Server so far (and where I'm currently stuck):
Under ConfigureServices():
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
options.IdentityResources = new IdentityResourceCollection
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email(),
new IdentityResource
{
Name = "roles",
DisplayName = "roles",
UserClaims = new List<string> { JwtClaimTypes.Role }
},
new IdentityResource
{
Name = "basicInfo",
DisplayName = "basic info",
UserClaims = new List<string> {
JwtClaimTypes.PreferredUserName
}
}
};
options.Clients = new ClientCollection
{
new Client
{
ClientId = "MyProjectAPI",
ClientName = "My Project Services API",
ClientSecrets = { new Secret("mysecret".Sha256()) },
AllowedGrantTypes = GrantTypes.Code,
AllowAccessTokensViaBrowser = true,
RedirectUris = { "https://localhost:44319/swagger/oauth2-redirect.html" },
PostLogoutRedirectUris = { "https://localhost:44319/Identity/Account/Logout" },
AllowedScopes = {
"basicInfo",
"roles",
"MyProjectServicesAPI",
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
RequirePkce = true,
RequireConsent = false
}
};
});
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerJwt()
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{
ValidateIssuer = true
};
});
And then in the pipeline:
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();
I recently got this error that's being thrown from Identity Server's OidcConfigurationController:
'Can't determine the type for the client 'MyProject''
I'm putting the Authorization Code type for the AllowedGrantTypes in my client so I'm not quite sure why it's throwing that error.
Do I need to be adding the claims to the Bearer token myself? If I'm including the scopes, why aren't those claims showing up? Thank you in advance for any assistance.
EDIT #1: I did resolve the error I was receiving from the OidcConfigurationController. I will add the JWT Bearer token only shows the 'MyProjectServicesAPI" scope and nothing else. However, my oidc discovery doc shows all of them?
I think I was able to partially solve my problem. So I didn't have Identity Server's Profile Service set up to grab my user's ID so it could grab the identity claims.
ProfileService.cs:
private readonly UserManager<ApplicationUser> _userManager;
public ProfileService(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
// Add custom claims to access token.
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
context.IssuedClaims.AddRange(context.Subject.Claims);
var user = await _userManager.GetUserAsync(context.Subject);
var roles = await _userManager.GetRolesAsync(user);
var claims = new List<Claim>
{
new Claim(JwtClaimTypes.Email, user.Email),
new Claim(JwtClaimTypes.PreferredUserName, user.UserName),
};
foreach (var claim in claims)
{
context.IssuedClaims.Add(claim);
}
foreach (var role in roles)
{
context.IssuedClaims.Add(new Claim(JwtClaimTypes.Role, role));
}
}
public async Task IsActiveAsync(IsActiveContext context)
{
var user = await _userManager.GetUserAsync(context.Subject);
context.IsActive = (user != null) && user.LockoutEnabled;
}
And then back in Startup.cs:
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
....
})
.AddProfileService<ProfileService>();
And that's it! Order does matter as I did have AddProfileService() before my AddApiAuthorization() and that didn't work. All of my scopes still aren't showing in my JWT token, so I will need to revisit that, even though the right claims are being pulled from those Identity resources.

How to implement remember me functionality properly? Asp.Net Core

I have been trying for 2 days to know how to build remember me functionality, but there is nothing clear.
First and foremost, I would like to make sure we agreed of the workflow of this properly as follows.
I need here to allow users to open their profile with no need to
signIn again for 1 month, as long as the user doesn't logOut.
I used cookie-based authentication to store some data that I can check every time when user profile opened to make sure that user is authenticated.
-- there is no problem with this step
I use in this step simple code to retrieve data again from the cookie.
-- and here is the problem comes. I can retrieve data from the cookie as long as I'm loggedIn, otherwise, when I stop and re-run the application and redirect to the user profile directly without logIn again I can't read the cookie data although it still exists!!!
Now let's take a look at code
Startup File Cookie Setting
public void ConfigureServices(IServiceCollection services){
.....
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options => {
options.Cookie.Name = "RememberMecookie"; // cookie name
options.LoginPath = "/Account/LogIn"; // view where the cookie will be issued for the first time
options.ExpireTimeSpan = TimeSpan.FromDays(30); // time for the cookei to last in the browser
options.SlidingExpiration = true; // the cookie would be re-issued on any request half way through the ExpireTimeSpan
options.EventsType = typeof(CookieAuthEvent);
});
.....
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
.....
app.UseAuthentication();
app.UseAuthorization();
app.UseCookiePolicy();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
}
.....
public class CookieAuthEvent : CookieAuthenticationEvents
{
public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)
{
context.Request.HttpContext.Items.Add("ExpiresUTC", context.Properties.ExpiresUtc);
}
}
}
Login ViewModel
public class VMLogin
{
public string UserName { get; set; }
public string Password { get; set; }
public bool RememberMe { get; set; }
}
Controller/Login
[HttpPost]
public async Task<IActionResult> LoginAsync(VMLogin CurrentUserLog, string returnUrl)
{
if (!string.IsNullOrEmpty(CurrentUserLog.UserName) && string.IsNullOrEmpty(CurrentUserLog.Password))
{
return RedirectToAction("Login");
}
if (ModelState.IsValid)
{
var SignInStatus = await signInManager.PasswordSignInAsync
(CurrentUserLog.UserName, CurrentUserLog.Password, CurrentUserLog.RememberMe, false);
AppUser _user = await userManager.FindByNameAsync(CurrentUserLog.UserName);
if (SignInStatus.Succeeded)
{
if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl)) // to prevent login from outside link
{
return Redirect(returnUrl);
}
else
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, CurrentUserLog.UserName),
new Claim(ClaimTypes.Email, _user.Email),
new Claim(ClaimTypes.NameIdentifier, _user.Id.ToString())
};
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
var props = new AuthenticationProperties{
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddMonths(1)
};
// to register the cookie to the browser
HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, props).Wait();
return RedirectToAction("UserProfile");
}
}
ModelState.AddModelError(string.Empty, "Invalid Login Attempt");
}
return View(CurrentUserLog);
}
Here is all the problem. I get data from the cookie when I logIn for the first time with the first creation of the cookie as shown in
the code above. However, I can't get the same date from the same
cookie when I stop debugging and run the app again, and redirect to
UserProfile directly without logIn, although the cookie "RememberMecookie" still exists.
Controller/UserProfile
[Authorize]
public async Task<IActionResult> UserProfile()
{
// all lines of code below are working just with the first creation of the cookie with the first login. but if rerun the app again, they all return null if redirect here directly without logIn.
string userId = User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value;
Claim v = HttpContext.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier);
AppUser _user = await userManager.GetUserAsync(HttpContext.User);
string cookieValueFromReq = Request.Cookies["RememberMecookie"];
// this is for normal login without remember me functionality
//AppUser user = await userManager.GetUserAsync(User);
return View(/*user*/);
}
Thanks For all guys who spent time checking out my question. I finally found the problem. This code is really great and it can be a good reference for remembering me functionality using cookie-based Authentication. And there is no problem with the code itself.
The problem was with my Startup file
It was like this
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options => {
options.Cookie.Name = "RememberMeBlogAcademy";
options.LoginPath = "/Account/LogIn";
//options.LogoutPath = "/Home/Index";
//options.AccessDeniedPath = "AccessDenied";
options.ExpireTimeSpan = TimeSpan.FromDays(30);
options.SlidingExpiration = true; // the cookie would be re-issued on any request half way through the ExpireTimeSpan
//options.Cookie.Expiration = TimeSpan.FromDays(5);
options.EventsType = typeof(CookieAuthEvent);
});
//services.AddScoped<CookieAuthEvent>();
services.AddControllersWithViews();
The problem was using MVC and AddControllersWithViews together. I didn't know that would make a problem.
However, It should be like this -- using AddControllersWithViews
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options => {
options.Cookie.Name = "RememberMeBlogAcademy";
options.LoginPath = "/Account/LogIn";
//options.LogoutPath = "/Home/Index";
//options.AccessDeniedPath = "AccessDenied";
options.ExpireTimeSpan = TimeSpan.FromDays(30);
options.SlidingExpiration = true; // the cookie would be re-issued on any request half way through the ExpireTimeSpan
//options.Cookie.Expiration = TimeSpan.FromDays(5);
options.EventsType = typeof(CookieAuthEvent);
});
services.AddScoped<CookieAuthEvent>();
services.AddControllersWithViews(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
Moreover, You don't need to retrieve data from the cookie as shown in Controller/UserProfile above.
Also, when I made debugging to check out the code I tested logout to make sure I really retrieve users data from the cookie not from UserManager and It really works well.
Here is the additional code of logOut
[Authorize]
public async Task<IActionResult> Logout()
{
await signInManager.SignOutAsync();
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return RedirectToAction("Index", "Home");
}

IdentityServer ASP.net CORE MVC ValidAudiences and Role is not working

I have a .net core MVC application, below are my queries
TargetFramework --> "net5.0"
IdentityModel Version--> "5.1.0"
Q1) In which I uses ValidAudiences which is not working. If I use ValidAudience token validation is working fine
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
//START for the cookie token based authentication
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
//Log Single Logout
services.AddTransient<CookieEventHandler>();
services.AddSingleton<LogoutSessionManager>();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
options.Cookie.Name = "mvc31_";
options.EventsType = typeof(CookieEventHandler);
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:5001";
options.ClientId = "testmvc31";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.SaveTokens = true;
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("roles");
//adding api1 scope to access api
options.Scope.Add("api1");
options.Scope.Add("offline_access");
options.GetClaimsFromUserInfoEndpoint = true;
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
//NameClaimType = JwtClaimTypes.Name,
RoleClaimType = "role",
ValidateIssuer = true,
ValidAudience = "testmvc31", //"testResource",
//ValidAudiences = new[] { "testResource1" }, //not working
ValidateAudience = true,
};
});
//END for the cookie token based authentication
}
ValidAudiences suppose to work. Do you have any idea why its not working?
Q2) In Token validation I mentioned that Role is attached to the claims name "role", In access_token I case see the role claims value, But I can not see role claim in User.Claims.ToList() and User.IsInRole("admin") is false ?
options.GetClaimsFromUserInfoEndpoint = true;
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role,
ValidateIssuer = true,
ValidAudience = "testResource1"
//ValidAudiences = new[] { "testResource1" },
ValidateAudience = true,
};
Access_token
eyJhbGciOiJSUzI1NiIsImtpZCI6IjIwMUY0QzVCMzRFNzA3QzhDOTBGNEFFMDgyMkNDRDMxNEZENjlFMzBSUzI1NiIsInR5cCI6IkpXVCIsIng1dCI6IklCOU1XelRuQjhqSkQwcmdnaXpOTVVfV25qQSJ9.eyJuYmYiOjE2MjI2OTg3ODEsImV4cCI6MTYyMjcwMjM4MSwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NTAwMSIsImF1ZCI6WyJUZXN0QVBJMSIsIlRlc3RBUEkyVGVzdCIsImh0dHBzOi8vbG9jYWxob3N0OjUwMDEvcmVzb3VyY2VzIl0sImNsaWVudF9pZCI6InRlc3RtdmMzMSIsInN1YiI6ImQ4NjBlZmNhLTIyZDktNDdmZC04MjQ5LTc5MWJhNjFiMDdjNyIsImF1dGhfdGltZSI6MTYyMjY5ODc4MSwiaWRwIjoibG9jYWwiLCJyb2xlIjoiYWRtaW4iLCJqdGkiOiI0MkYyRTNEOEY3MkQ5NDQxMDAwQTdCMEI3Q0UyRTA0QSIsInNpZCI6IkE4QzVCMTRDQzgxMTc3RDFFRDlGRDdEQzVGNkZEMEY1IiwiaWF0IjoxNjIyNjk4NzgxLCJzY29wZSI6WyJvcGVuaWQiLCJwcm9maWxlIiwicm9sZXMiLCJhcGkxIiwib2ZmbGluZV9hY2Nlc3MiXSwiYW1yIjpbInB3ZCJdfQ.dA4JOxeWd0cGlzr5BSANNL3ZZATzxchgFwLivQVH4rbyfPr6LRIvep6-NjrNEOL_YvJVCDIEU7TBC0-9qBZVL6OgmjDZBZ5dapNhD8ZZP39bUnfqXLJqRAQgR3yeIlB60EQ3vDEnLen1HZuZJCDoqzXr-sANp75IEOLYPxfDFE5SCljex_zX9AQ1dzAUF4k60N3nbJWwn1aqOM3TdKBG85O_QDWZ-FCg5-7FI55HyrJaF4Ojb6qrFf6WdumWnz6_8sT4r9734X2QftRFeFkId36shUJpxqC-zpf5PJYjgg_rhMZ68vFuWONzKFSbXiYhqoMzCa4JzZItF_9bonXELQ
Id_token
eyJhbGciOiJSUzI1NiIsImtpZCI6IjIwMUY0QzVCMzRFNzA3QzhDOTBGNEFFMDgyMkNDRDMxNEZENjlFMzBSUzI1NiIsInR5cCI6IkpXVCIsIng1dCI6IklCOU1XelRuQjhqSkQwcmdnaXpOTVVfV25qQSJ9.eyJuYmYiOjE2MjI3MDUzMjYsImV4cCI6MTYyMjcwNTYyNiwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NTAwMSIsImF1ZCI6InRlc3RtdmMzMSIsIm5vbmNlIjoiNjM3NTgzMDIxMjQ5NzE2OTQ0LllqWmxaVFUxT1dFdE4yUTVOUzAwWWpSaUxUZzFZamN0TldKbFl6YzJPV0V3TkRnek0yWmxaamcyWkRrdE9Ea3dNaTAwTnpReExUZ3hZekV0TXpReFpqaGlaRGt4WkdFNCIsImlhdCI6MTYyMjcwNTMyNiwiYXRfaGFzaCI6Ik50OTV2WWtsVlg4dnEtNlFENkVKNVEiLCJzX2hhc2giOiJqV1R1NUxxZTdob0xxQ3lnSm4tSGlnIiwic2lkIjoiRDkxNDg1RjdCMkU5QTgxRENEODMyQzZGQ0VEMzA3MUEiLCJzdWIiOiJkODYwZWZjYS0yMmQ5LTQ3ZmQtODI0OS03OTFiYTYxYjA3YzciLCJhdXRoX3RpbWUiOjE2MjI3MDUzMjYsImlkcCI6ImxvY2FsIiwiYW1yIjpbInB3ZCJdfQ.KAqpxfctknB5DV8_leOekZajdgYOJ_sLsa12Hx1-qmOS8hLfN0RwpE3MCGyAiKhSEEPwGPXbbAglZnZKDKbH48RwsA-Zvu3Z8qO3_UCgG6U_ghTW3FSHWV2BJMpM_-OCvqq6pwx65Wh_9-u9xRq3F5r6SbgAyEwzjUE925UOMrWgnyWQAVCuVZ-7W2nO3pkLhf5NW0ItUcF5I6TJn54wgcga-JP1rNh2gIIMT4N9ijfcdbbLVrX6wFqKlXEUWOzGW9m8A8oCZ5ZvbJ_iy3mNT-H3DjMO6K-FP2hwNeU7X3nhdvdrVM6_CyvfLQr9bHQVQB9Aj42DAFOQCfR4V2NtNQ
Any idea about this two issues?
ValidAudiences in your API must match the aud claim in the access token.
You need to use for example:
ValidAudience = "testAPI"
For the role issue, can you paste a compelte sample access token?
Usually, you need to tell AddJwtBearer the name of your role claim, using:
opt.TokenValidationParameters.RoleClaimType = "role";
opt.TokenValidationParameters.NameClaimType = "name";
Similar to what you have done, so that should work.
Are you sure your role claim in the token is role, not roles?
The access token is not meant to be consumed or inspected by the client application, its only to be consumed by the API. The ID-token is used to create the "user" in the client ASP.NET Core application.
To get user claims into it, its just a matter to add them as a userclaims, like:
new IdentityResource(name: "employee", userClaims: new string[] { "role", "employeetype", "IsCeo" })
Don't worry if you don't see the role in the IDToken,it will otherwise be retrieved from the UserInfo in a separate requests.
You control this using this flag in the Client definition:
// When requesting both an id token and access token, should the user claims always
// be added to the id token instead of requiring the client to use the UserInfo endpoint.
// Defaults to false.
AlwaysIncludeUserClaimsInIdToken = false,
In AddOpenIDConnect you also have this flag:
options.GetClaimsFromUserInfoEndpoint = true;

NET Core 3.1 MVC Authorization/Authentication with token (JWT) obtained externally in separate Net Core 3.1 Web Api

I have 3 projects:
Net Core 3.1 MVC project.
Net Core 3.1 Web Api project with JWT auth --> connected to db via Entity Framework
(Xamarin app that also uses the web api for authentication and data retrieval).
I do not want to connect separately to the same db from both projects (1,2) (does not feel like a good idea, correct me if im wrong, would like to keep the db crud operations contained to the web api).
I want to do the authentication in the Web Api project and pass the token to the Net Core MVC project.
I do not know how to use the token to authorize this user for the MVC
project, so that controllers can be accessed based on the user's role
etc. Basically log this user in in the MVC project with the token
obtained in the web api. Is this even possible or a proper method? Any
help please?
MVC project
Startup.cs class
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
// add for razor pages and refresh without rebuilding
services.AddRazorPages().AddRazorRuntimeCompilation();
// add httpClient
services.AddHttpClient();
// start auth jwt
services.AddSession(options => {
options.IdleTimeout = TimeSpan.FromMinutes(1);
});
//services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
//Provide a secret key to Encrypt and Decrypt the Token
var SecretKey = Encoding.ASCII.GetBytes
("mySecretKeyForAuthenticationAndAuthorization");
//Configure JWT Token Authentication
services.AddAuthentication(auth =>
{
auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(token =>
{
token.RequireHttpsMetadata = false;
token.SaveToken = true;
token.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
//Same Secret key will be used while creating the token
IssuerSigningKey = new SymmetricSecurityKey(SecretKey),
ValidateIssuer = true,
//Usually, this is your application base URL
ValidIssuer = "https://myAzureWebAPi.azurewebsites.net",
ValidateAudience = true,
//Here, we are creating and using JWT within the same application.
//In this case, base URL is fine.
//If the JWT is created using a web service, then this would be the consumer URL.
ValidAudience = "https://mylocalhost/",
RequireExpirationTime = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
});
// end auth jwt
// add for roles authorization
services.AddAuthorization(config =>
{
config.AddPolicy(Policies.Admin, Policies.AdminPolicy());
config.AddPolicy(Policies.Client, Policies.ClientPolicy());
});
services.AddControllersWithViews();
}
// 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();
}
else
{
app.UseExceptionHandler("/Home/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.UseHttpsRedirection();
app.UseStaticFiles();
//add for jwt
app.UseCookiePolicy();
app.UseSession();
//Add JWToken to all incoming HTTP Request Header
app.Use(async (context, next) =>
{
var JWToken = context.Session.GetString("JWToken");
if (!string.IsNullOrEmpty(JWToken))
{
context.Request.Headers.Add("Authorization", "Bearer " + JWToken);
}
await next();
});
app.UseRouting();
// added for jwt
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{area=Client}/{controller=LoginPage}/{action=Index}/{id?}");
});
}
}
MVC LoginPageController.cs
[Area("Client")]
public class LoginPageController : Controller
{
private readonly IHttpClientFactory _clientFactory;
public LoginPageController(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public IActionResult Index()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Login([Bind] LoginModel loginModel)
{
var client = _clientFactory.CreateClient();
//var client = new HttpClient();
try
{
var json = JsonConvert.SerializeObject(loginModel);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var outcome = await client.PostAsync("https://<AzureWebAPiUrl>/api/accounts/login", content);
if (outcome!= null && outcome.IsSuccessStatusCode)
{
var jsonResult = await outcome.Content.ReadAsStringAsync();
var token = JsonConvert.DeserializeObject<Token>(jsonResult);
Console.WriteLine(token.user_role);
// store token in a session
HttpContext.Session.SetString("JWToken", token.access_token);
// Here is the problem, once I have the token how do I make this user be
//authenticated in the mvc project so that the [Authorize[Role = "someRole"] on controllers works
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return RedirectToAction("Index", "AdminDeals", new { area = "Admin"}); // only if role is admin
}
}
Hey I have solution for this please refer below point
first of all you need to add authentication. public void ConfigureServices(IServiceCollection services)
services.AddSession();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = >JwtBearerDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = >JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
// Adding Jwt Bearer
.AddJwtBearer(options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = Configuration["JWTConfig:ValidAudience"],
ValidIssuer = Configuration["JWTConfig:ValidIssuer"],
IssuerSigningKey = new >SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWTConfig:Secret"]))
};
});
After that you have to Use Session for storing authentication token and in this token you have to encrypt token combination of role list whatever role want to pass for the authorization.
Here i have used JWT Bearer token
Using this session you have to configure in public void Configure(IApplicationBuilder app, IWebHostEnvironment env)startup.cs file for use header authentication.
app.UseSession();
app.Use(async (context, next) =>
{
var token = context.Session.GetString("Token");
if (!string.IsNullOrEmpty(token))
{
context.Request.Headers.Add("Authorization", "Bearer " + token);
}
await next();
});
then after you you have to add in your controller
[Authorize(Roles = "Employee,Student")]
public ActionResult Leave()
{
// your code here
}
This is a typical scenario that you should implement OAuth2.0 for your application- You have 2 kinds of clients (MVC and Xamrin), both of them need to be authenticated then access your API project, your API should be protected by a identity provider instead of doing the authenticate by itself. In asp.net core, the most popular solution is Identity Server 4, you don't have to reinvent the wheel , just create an Identity Provider sever, and config your API and MVC project based on the instruction in their documents and everything is ok then. Meanwhile, Identity Server 4 support entity framework

MVC 5 with forms authentication and interacting with Bearer token web api back end

I have a mvc 5 app that uses forms authentication but the real Authentication of user happens using bearer token in web api . I'm adding token details in the cookie so the website is always authenticated. MVC and Web api are in same project. Web api hosted using Owin.
here is my code snippet.
startup.cs
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
//Configure authorization
ConfigureOAuth(app);
//register WebAPI
app.UseWebApi(ConfigureWebApiRoutes());
}
}
startup.auth.cs
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Login/Login"),
CookieHttpOnly = true,
//AuthenticationMode = AuthenticationMode.Passive,
CookieName = "YetAnotherTodo.WebApi.Auth",
//#if DEBUG
// CookieSecure = CookieSecureOption.Never
//#endif
});
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
// using OAuth authentication server as authentication middle ware and Token Generation
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider(),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
#if DEBUG
AllowInsecureHttp = true
#endif
});
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
code in MVC Login Controller
[AllowAnonymous]
[HttpPost]
public async Task<ActionResult> Login(LoginViewModel model, string redirectUrl = null)
{
if (!ModelState.IsValid) return View(model);
try
{
if (string.IsNullOrWhiteSpace(redirectUrl))
{
redirectUrl = "~/Home";
}
var result = await WebApiService.Instance.AuthenticateAsync<LogInResult>(model.UserName, model.Password);
//Let's keep the user authenticated in the MVC webapp.
//By using the AccessToken, we can use User.Identity.Name in the MVC controllers to make API calls.
FormsAuthentication.SetAuthCookie(result.AccessToken, model.RememberMe);
//Create an AuthenticationTicket to generate a cookie used to authenticate against Web API.
//But before we can do that, we need a ClaimsIdentity that can be authenticated in Web API.
var claims = new[]
{
new Claim(ClaimTypes.Name, model.UserName),
//Name is the default name claim type, and UserName is the one known also in Web API.
new Claim(ClaimTypes.NameIdentifier, model.UserName)
//If you want to use User.Identity.GetUserId in Web API, you need a NameIdentifier claim.
};
//Generate a new ClaimsIdentity, using the DefaultAuthenticationTypes.ApplicationCookie authenticationType.
//This also matches what we've set up in Web API.
var claimsIdentity = new ClaimsIdentity(claims,DefaultAuthenticationTypes.ApplicationCookie);
var authProperties = new AuthenticationProperties
{
ExpiresUtc = result.Expires,
IsPersistent = model.RememberMe,
IssuedUtc = result.Issued,
RedirectUri = redirectUrl
};
var authTicket = new AuthenticationTicket(claimsIdentity, authProperties);
//And now it's time to generate the cookie data. This is using the same code that is being used by the CookieAuthenticationMiddleware class in OWIN.
byte[] userData = DataSerializers.Ticket.Serialize(authTicket);
//Protect this user data and add the extra properties. These need to be the same as in Web API!
//byte[] protectedData = MachineKey.Protect(userData, new[] { "Microsoft.Owin.Security.Cookies.CookieAuthenticationMiddleware", DefaultAuthenticationTypes.ApplicationCookie, "v1" });
//base64-encode this data.
string protectedText = TextEncodings.Base64Url.Encode(userData);
//And now, we have the cookie.
Response.SetCookie(new HttpCookie("YetAnotherTodo.WebApi.Auth")
{
HttpOnly = true,
Expires = result.Expires.UtcDateTime,
Value = protectedText
});
Code in my provider that generates token
AuthenticationTicket ticket;
var cookiesIdentity = GenerateCookiesIdentity(context, user, out ticket);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
I was able to login and able to get ticket from token server but on subsequent request or redirect to home page after logging in , I'm still getting 401 error.
This is kinda combinations of these two blogs/tutorials : Blog 1 and Blog 2

Resources