I have an web application written in .Net Core both Backend + Frontend
I have login page with Email + Password + Remember Me ( Checkbox )
I want if user click on remember me stay signed in forever until he/she clear cookies.
When user logging in I save ACCESS_TOKEN + REFRESH_TOKEN in Principals Here is example of Login Method:
public async Task<IActionResult> LoginAsync(LoginViewModel loginViewModel)
{
if (ModelState.IsValid)
{
var requestDto = _mapper.Map<LoginRequestDto>(loginViewModel);
try
{
var response = await _authenticationClient.LoginAsync(requestDto);
var claimsIdentity = new ClaimsIdentity(new[]
{
new Claim(CustomUserClaimTypes.AccessToken, response.AccessToken),
new Claim(CustomUserClaimTypes.RefreshToken, response.RefreshToken),
new Claim(CustomUserClaimTypes.FullName, response.Fullname),
new Claim(CustomUserClaimTypes.UserName, response.Username),
new Claim(CustomUserClaimTypes.UserId, response.UserId),
new Claim(CustomUserClaimTypes.Email, response.Email),
new Claim(CustomUserClaimTypes.TokenExpireTime, response.TokenExpire.ToString())
}, CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignOutAsync(scheme: CookieAuthenticationDefaults.AuthenticationScheme);
var principals = new ClaimsPrincipal(claimsIdentity);
if (loginViewModel.IsPersistent)
{
await HttpContext.SignInAsync(scheme: CookieAuthenticationDefaults.AuthenticationScheme, principals, new AuthenticationProperties
{
IsPersistent = true,
AllowRefresh = true,
IssuedUtc = DateTime.Now
});
}
else
{
await HttpContext.SignInAsync(scheme: CookieAuthenticationDefaults.AuthenticationScheme, principals, new AuthenticationProperties { IsPersistent = false, AllowRefresh = true });
}
return new JsonResult(new
{
success = true,
result = "/home"
});
}
catch (ApiException ex)
{
if (ex.ErrorResponse != null)
{
return new JsonResult(new
{
success = false,
result = ex.ErrorResponse.Errors.Select(x => x.Message)
});
}
if (ex.Response != null)
{
return new JsonResult(new
{
success = false,
result = ex.Response
});
}
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
return View("Index");
}
Token Expire Time is 5 Minutes, I want if user clicked remember me after this 5 minutes I need to refresh token to make sure access token is valid
Here is example of Refresh Token :
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.LoginPath = "/Authentication/Index";
options.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = async x =>
{
var identity = (ClaimsIdentity)x.Principal.Identity;
var tokenExpire = identity.FindFirst(CustomUserClaimTypes.TokenExpireTime);
if (DateTime.Parse(tokenExpire.Value) < DateTime.Now)
{
var accessTokenClaim = identity.FindFirst(CustomUserClaimTypes.AccessToken);
var refreshTokenClaim = identity.FindFirst(CustomUserClaimTypes.RefreshToken);
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("https://test.api.com/api/v1/");
var requestData = new RefreshTokenRequestApiDto
{
AccessToken = accessTokenClaim.Value,
RefreshToken = refreshTokenClaim.Value
};
var jsonData = Newtonsoft.Json.JsonConvert.SerializeObject(requestData);
var stringContent = new StringContent(jsonData, Encoding.UTF8, "application/json");
var result = await client.PostAsync("auth/refresh-token", stringContent);
if (result.IsSuccessStatusCode)
{
var deserializedData = await result.Content.ReadAsStringAsync();
var mapped = Newtonsoft.Json.JsonConvert.DeserializeObject<BaseApiResponse<BaseAuthenticationResponseDto>>(deserializedData);
if (mapped != null)
{
identity.RemoveClaim(accessTokenClaim);
identity.RemoveClaim(refreshTokenClaim);
identity.RemoveClaim(tokenExpire);
identity.AddClaims(new[]
{
new Claim(CustomUserClaimTypes.AccessToken, mapped.Result.AccessToken),
new Claim(CustomUserClaimTypes.RefreshToken, mapped.Result.RefreshToken),
new Claim(CustomUserClaimTypes.TokenExpireTime, mapped.Result.TokenExpire.ToString())
});
x.ShouldRenew = true;
}
}
}
}
}
};
});
The problem is when I try on localhost everything working good, User is persist, even I turn off my PC, But when I publish this to my PLESK panel, and Try it from there, after 5 minutes user signed out and on cookie the AspNet.Cookies is exist but user seems unauthorized, I need to login again after 5 minutes continuously.
Anything wrong on my code ?
Or I need to do something on my Plesk Panel ?
SORRY ABOUT MY ENGLISH :)
Thanks!
Related
I've upgraded a Blazor Wasm app to .Net 6, by creating new projects and then added MS Identity (not Identity Server) from Chris Sainty's blog. Authorisation looks ok & the user can logon. However the claims are not available to the api controller. This is because it looks like the Authorisation header is not being set for the http client call. If I explicitly set the DefaultRequestHeaders.Authorization to the jwt token then I get the claims in the controller ok. I therefore assume the issue is in how I setup the http client (I thought ApiAuthenticationStateProvider would have done this). Any help would be greatly appreciated.
The relevant parts are:
Client: AuthService:
public async Task<LoginResponse> Login(LoginModel loginModel)
{
loginModel.BaskeyKey = await _localStorageService.GetItemAsync<string>(LocalStorageConstants.BasketKey);
var loginAsJson = JsonSerializer.Serialize(loginModel);
var response = await _httpClient.PostAsync("api/Login", new StringContent(loginAsJson,
Encoding.UTF8, "application/json"));
var loginResult = JsonSerializer.Deserialize<LoginResponse>(
await response.Content.ReadAsStringAsync(),
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
if (!response.IsSuccessStatusCode)
{
return loginResult;
}
await _localStorageService.SetItemAsync("authToken", loginResult.Token);
((ApiAuthenticationStateProvider)_authenticationStateProvider).MarkUserAsAuthenticated(loginModel.Email);
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("bearer", loginResult.Token);
_basketStateService.SetBasket(loginResult.LoginResponseDto.BasketDto);
return loginResult;
}
Server Startup:
builder.Services.AddDefaultIdentity<ApplicationUser>(options =>
{
options.SignIn.RequireConfirmedEmail = true;
options.SignIn.RequireConfirmedAccount = true;
})
.AddEntityFrameworkStores<LTDbContext>();
builder.Services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier;
});
var jwtSettings = builder.Configuration.GetSection("Jwt");
builder.Services.AddAuthentication(auth =>
{
auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtSettings["JwtIssuer"],
ValidAudience = jwtSettings["JwtAudience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(jwtSettings["JwtSecurityKey"]))
};
options.MapInboundClaims = false;
});
and client setup for httpClient (I need an authorised & unauthorised client - I think)
builder.Services.AddHttpClient(AuthConstants.Auth, client =>
{
client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress);
});
builder.Services.AddHttpClient(AuthConstants.NoAuth,
client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));
builder.Services.AddScoped(sp =>
sp.GetRequiredService<IHttpClientFactory>().CreateClient(AuthConstants.Auth));
and Client - ApiAuthenticationStateProvider
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var savedToken = await _localStorageService.GetItemAsync<string>("authToken");
if (string.IsNullOrWhiteSpace(savedToken))
{
return new AuthenticationState(new ClaimsPrincipal(
new ClaimsIdentity()));
}
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", savedToken);
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(savedToken), "jwt")));
}
public void MarkUserAsAuthenticated(string email)
{
var authenticatedUser = new ClaimsPrincipal(
new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, email),
new Claim(ClaimTypes.NameIdentifier, email),
new Claim(ClaimTypes.Email, email)
},
"apiauth"
));
var authState = Task.FromResult(new AuthenticationState(authenticatedUser));
NotifyAuthenticationStateChanged(authState);
}
I started code own site with backend ASP.NET Core and React. I get a basic code to create an ASP backend with authentication from this
https://jasonwatmore.com/post/2019/10/14/aspnet-core-3-simple-api-for-authentication-registration-and-user-management#running-react
I created an endpoint to my API which allows adding a photo to my server and It's working
[HttpPost("{username}/photos/")]
public IActionResult AddPhoto(String username, [FromForm]IFormFile photo)
{
//add img to static folder
var wwww_root = _webHostEnvironment.WebRootPath;
var path = #"\profiles\" + username + #"\profile_photos\";
var upload_path = wwww_root + path;
var pathForFile = upload_path + photo.FileName;
try
{
using (FileStream fileStream = System.IO.File.Create(pathForFile))
{
photo.CopyTo(fileStream);
fileStream.Flush();
}
}
catch(DirectoryNotFoundException e)
{
return NotFound("This username does not exists");
}
var pathOnServer = "http://" + Request.Host.Value + path + photo.FileName;
var photoImage = _profileService.AddPhoto(username, pathOnServer);
return Ok();
}
but I want to also add authentication to use this endpoint based on the given "username"
i.e. this endpoint returns OK when the Bearer Token is the same as that assigned to the username.
In the given link I found a method configuring the authentication:
public void ConfigureServices(IServiceCollection services)
{
// use sql server db in production and sqlite db in development
if (_env.IsProduction())
services.AddDbContext<DataContext>();
else
services.AddDbContext<DataContext, SqliteDataContext>();
services.AddCors();
services.AddControllers();
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
// configure strongly typed settings objects
var appSettingsSection = _configuration.GetSection("AppSettings");
services.Configure<AppSettings>(appSettingsSection);
// configure jwt authentication
var appSettings = appSettingsSection.Get<AppSettings>();
var key = Encoding.ASCII.GetBytes(appSettings.Secret);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var userService = context.HttpContext.RequestServices.GetRequiredService<IUserService>();
var userId = int.Parse(context.Principal.Identity.Name);
var user = userService.GetById(userId);
if (user == null)
{
// return unauthorized if user no longer exists
context.Fail("Unauthorized");
}
return Task.CompletedTask;
}
};
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
And api endpoint to Authenticate:
[AllowAnonymous]
[HttpPost("authenticate")]
public IActionResult Authenticate([FromBody]AuthenticateModel model)
{
var user = _userService.Authenticate(model.Username, model.Password);
if (user == null)
return BadRequest(new { message = "Username or password is incorrect" });
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, user.Id.ToString())
}),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
// return basic user info and authentication token
return Ok(new
{
Id = user.Id,
Username = user.Username,
FirstName = user.FirstName,
LastName = user.LastName,
Token = tokenString
});
}
but I can't translate this into a solution to my problem.
I am asking for tips on how I can do it
EDIT 1
You can check the claims and token in your upload method
In ConfigureService add this
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
In the upload method use this:
// works if the token is in the header
var tokenFromHeader = HttpContext.Request.Headers["Authorization"];
// or try this if .Net Core is < 3.0
var tokenFromAuth = await HttpContext.Authentication.GetTokenAsync("access_token");
// Get Claims
var identity = HttpContext.User.Identity as ClaimsIdentity;
// Get user id from claims.
var userIdClaim = identity.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Name);
// Use the value.
if (userIdClaim != null && userIdClaim.value != null) {
// your logic here
}
To check the authorization on api call set the attribute [Authorize] on your controller or on your method.
[Authorize]
[HttpPost("{username}/photos/")]
public IActionResult AddPhoto(String username, [FromForm]IFormFile photo)
More info here:
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/?view=aspnetcore-3.1
The basic code:
Startup.cs -> ConfigureServices
// configure jwt authentication
var jwtSettings = jwtSettingsSection.Get<JwtSettings>();
var key = Encoding.ASCII.GetBytes(jwtSettings.JwtKey);
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 = true,
ValidIssuer = jwtSettings.JwtIssuer,
ValidateAudience = true,
ValidAudience = jwtSettings.JwtAudience,
//RequireExpirationTime = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
});
Startup.cs -> Configure
app.UseAuthentication();
app.UseAuthorization();
Authentication Controller
[AllowAnonymous]
[HttpPost()]
public async Task<IActionResult> AuthenticateUser([FromBody] UserAuthentication userResource)
{
//Your logic of authorization here
//if not authorized
//return Unauthorized();
var tokenString = CreateJwtToken(user);
// return basic user info (without password) and token to store client side
return Ok(new
{
Token = tokenString
});
}
Create JWT Token
public string CreateJwtToken(McUsers user)
{
var key = Encoding.ASCII.GetBytes(jwtSettings.JwtKey);
var tokenDescriptor = new SecurityTokenDescriptor
{
Issuer = jwtSettings.JwtIssuer,
Audience = jwtSettings.JwtAudience,
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.GivenName, user.FirstName),
new Claim(ClaimTypes.Surname, user.LastName),
new Claim(ClaimTypes.Role, user.UserRole.Description)
}),
NotBefore = DateTime.UtcNow,
Expires = DateTime.UtcNow.AddMinutes(120),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature)
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
return tokenString;
}
Your method in the controller
[Authorize]
[HttpPost("{username}/photos/")]
public IActionResult AddPhoto(String username, [FromForm]IFormFile photo)
{
//add img to static folder
var wwww_root = _webHostEnvironment.WebRootPath;
var path = #"\profiles\" + username + #"\profile_photos\";
var upload_path = wwww_root + path;
var pathForFile = upload_path + photo.FileName;
try
{
using (FileStream fileStream = System.IO.File.Create(pathForFile))
{
photo.CopyTo(fileStream);
fileStream.Flush();
}
}
catch(DirectoryNotFoundException e)
{
return NotFound("This username does not exists");
}
var pathOnServer = "http://" + Request.Host.Value + path + photo.FileName;
var photoImage = _profileService.AddPhoto(username, pathOnServer);
return Ok();
}
I'm using identityserver4 with .Net core 3.0 Azure AD authentication for my application.
When user logged in to Microsoft account successfully it should be redirect to angular client application.
Unfortunately I'm getting below error when redirects to the client application.
Below is my OIDC configuration
services.AddAuthentication()
.AddOpenIdConnect("aad", "Demo IdentityServer", options =>
{
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = async ctx =>
{
var message = ctx.ProtocolMessage;
ctx.Properties.Items[OpenIdConnectDefaults.UserstatePropertiesKey] = message.State;
}
};
options.CallbackPath = "/signin-oidc";
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.SaveTokens = true;
options.Authority = "xxxxxxxxxxxx",
options.ClientId = "xxxxxxxxxxxx",
options.ResponseType = "code id_token";
options.ClientSecret = "L-soqX=3N8kIU0y1gDbB=Ws_QqAMUw#[";
options.GetClaimsFromUserInfoEndpoint = true;
options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
//,RoleClaimType = "role"
};
});
return services;
This is my call back method.
public async Task<IActionResult> Callback()
{
// read external identity from the temporary cookie
try
{
var result = await HttpContext.AuthenticateAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme);
if (result?.Succeeded != true)
{
throw new Exception("External authentication error");
}
if (_logger.IsEnabled(LogLevel.Debug))
{
var externalClaims = result.Principal.Claims.Select(c => $"{c.Type}: {c.Value}");
_logger.LogDebug("External claims: {#claims}", externalClaims);
}
// lookup our user and external provider info
var (user, provider, providerUserId, claims) = FindUserFromExternalProvider(result);
if (user == null)
{
user = AutoProvisionUser(provider, providerUserId, claims);
}
var additionalLocalClaims = new List<Claim>();
var localSignInProps = new AuthenticationProperties();
ProcessLoginCallbackForOidc(result, additionalLocalClaims, localSignInProps);
var isuser = new IdentityServerUser(user.SubjectId)
{
DisplayName = user.Username,
IdentityProvider = provider,
AdditionalClaims = additionalLocalClaims
};
await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.SubjectId, user.Username));
await HttpContext.SignInAsync(user.SubjectId, user.Username, provider, localSignInProps, additionalLocalClaims.ToArray());
await HttpContext.SignOutAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme);
// retrieve return URL
var returnUrl = result.Properties.Items["returnUrl"] ?? "~/";
// check if external login is in the context of an OIDC request
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
//await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.SubjectId, user.Username, true, context?.ClientId));
return Redirect(returnUrl);
}
catch (Exception ex)
{
throw;
}
}
Any help would be appreciate in advance.
I am implementing Aspnet.security.openidconnect (ASOS) with .net core 2.1 application. Now the issue is when I am trying to execute this chunk in controller,
public async Task<IActionResult> Authorize()
{
if (Response.StatusCode != 200)
{
return View("AuthorizeError");
}
var ticket = await AuthenticationHttpContextExtensions.AuthenticateAsync(HttpContext, CookieAuthenticationDefaults.AuthenticationScheme);
var identity = ticket != null && ticket.Principal != null ? ticket.Ticket.Principal : null;
if (identity == null)
{
await AuthenticationHttpContextExtensions.ChallengeAsync(HttpContext, CookieAuthenticationDefaults.AuthenticationScheme, null);
return Unauthorized();
}
ViewData["Name"] = ticket.Principal.Identity.Name;
var scopes = (HttpContext.Request.Query["scope"].ToString() ?? "").Split(' ');
ViewData["Scopes"] = scopes;
//var claimsIdentity = new ClaimsIdentity(identity.Claims, "Bearer", identity.NameClaimType, identity.RoleClaimType);
var claimsIdentity = new ClaimsIdentity(identity.Claims, "Bearer");
foreach (var scope in scopes)
{
claimsIdentity.AddClaim(new Claim("urn:oauth:scope", scope));
}
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
await AuthenticationHttpContextExtensions.SignInAsync(HttpContext, claimsPrincipal);
logger.Info("Authorize request received");
return View();
}
The error I am getting on this line:
var ticket = await AuthenticationHttpContextExtensions.AuthenticateAsync(HttpContext, CookieAuthenticationDefaults.AuthenticationScheme);
And here is the implementation of ASOS in startup:
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;
});
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie("Application", options =>
{
options.LoginPath = new PathString(LoginPath);
options.LogoutPath = new PathString(LogoutPath);
options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
//options.AccessDeniedPath = new PathString();
});
//services.AddAuthentication("External")
// .AddCookie("Cookies", options =>
// {
// options.Cookie.Name = CookieAuthenticationDefaults.CookiePrefix + "External";
// options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
// });
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
services.AddAuthentication(OAuthValidationDefaults.AuthenticationScheme).AddOAuthValidation()
.AddOpenIdConnectServer(options =>
{
options.AuthorizationEndpointPath = new PathString(AuthorizePath);
// Enable the token endpoint.
options.TokenEndpointPath = new PathString(TokenPath);
options.ApplicationCanDisplayErrors = true;
options.AccessTokenLifetime = TimeSpan.FromMinutes(5);
#if DEBUG
options.AllowInsecureHttp = true;
#endif
options.Provider.OnValidateAuthorizationRequest = context =>
{
if (string.Equals(context.ClientId, Configuration["OpenIdServer:ClientId"], StringComparison.Ordinal))
{
context.Validate(context.RedirectUri);
}
return Task.CompletedTask;
};
// Implement OnValidateTokenRequest to support flows using the token endpoint.
options.Provider.OnValidateTokenRequest = context =>
{
// Reject token requests that don't use grant_type=password or grant_type=refresh_token.
if (!context.Request.IsClientCredentialsGrantType() && !context.Request.IsPasswordGrantType()
&& !context.Request.IsRefreshTokenGrantType())
{
context.Reject(
error: OpenIdConnectConstants.Errors.UnsupportedGrantType,
description: "Only grant_type=password and refresh_token " +
"requests are accepted by this server.");
return Task.CompletedTask;
}
if (string.IsNullOrEmpty(context.ClientId))
{
context.Skip();
return Task.CompletedTask;
}
if (string.Equals(context.ClientId, Configuration["OpenIdServer:ClientId"], StringComparison.Ordinal) &&
string.Equals(context.ClientSecret, Configuration["OpenIdServer:ClientSecret"], StringComparison.Ordinal))
{
context.Validate();
}
return Task.CompletedTask;
};
// Implement OnHandleTokenRequest to support token requests.
options.Provider.OnHandleTokenRequest = context =>
{
// Only handle grant_type=password token requests and let
// the OpenID Connect server handle the other grant types.
if (context.Request.IsClientCredentialsGrantType() || context.Request.IsPasswordGrantType())
{
//var identity = new ClaimsIdentity(context.Scheme.Name,
// OpenIdConnectConstants.Claims.Name,
// OpenIdConnectConstants.Claims.Role);
ClaimsIdentity identity = null;
if (context.Request.IsClientCredentialsGrantType())
{
identity = new ClaimsIdentity(new GenericIdentity(context.Request.ClientId, "Bearer"), context.Request.GetScopes().Select(x => new Claim("urn:oauth:scope", x)));
}
else if (context.Request.IsPasswordGrantType())
{
identity = new ClaimsIdentity(new GenericIdentity(context.Request.Username, "Bearer"), context.Request.GetScopes().Select(x => new Claim("urn:oauth:scope", x)));
}
// Add the mandatory subject/user identifier claim.
// By default, claims are not serialized in the access/identity tokens.
// Use the overload taking a "destinations" parameter to make sure
// your claims are correctly inserted in the appropriate tokens.
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, Guid.NewGuid().ToString("n") + Guid.NewGuid().ToString("n"), OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken);
var ticket = new Microsoft.AspNetCore.Authentication.AuthenticationTicket(
new ClaimsPrincipal(identity),
new Microsoft.AspNetCore.Authentication.AuthenticationProperties(),
context.Scheme.Name);
// Call SetScopes with the list of scopes you want to grant
// (specify offline_access to issue a refresh token).
ticket.SetScopes(
OpenIdConnectConstants.Scopes.Profile,
OpenIdConnectConstants.Scopes.OfflineAccess);
context.Validate(ticket);
}
return Task.CompletedTask;
};
Now the error I am getting is:
InvalidOperationException: No authentication handler is registered for
the scheme 'Cookies'. The registered schemes are: Application, Bearer,
ASOS. Did you forget to call
AddAuthentication().AddSomeAuthHandler?
What am I missing here. Any help?
So found the issue, actually I was using "Application" name for cookie scheme and in controller I was using default name "Cookies". So just had to remove the explicit "Application" name to default "Cookies" name
No authenticationScheme was specified, and there was no DefaultChallengeScheme found Cookies Authentication
In my case I was using "Cookies" when adding authentication and "Cookie" when calling the SiginOut method.
Changed both the places to use "Cookies"
Startup:
services.AddAuthentication(config => {
config.DefaultScheme = "Cookies";
config.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")<---- Change here.
.AddOpenIdConnect("oidc", config => {
config.Authority = "https://localhost:44392/";
config.ClientId = "client_id_mvc";
config.ClientSecret = "client_secret_mvc";
config.SaveTokens = true;
config.ResponseType = "code";
//config.SignedOutCallbackPath = "/Privacy";
});
Calling SignOut:
public async Task<IActionResult> OnPostAsync()
{
return SignOut("Cookies", "oidc");
}
I have a asp.net web application using razor pages with PageModel. I need to access a REST web service to get the JWT token
This is my Service configuration within Startup.cs
services.AddIdentity<AppUser, AppUserRole>(cfg =>
{
cfg.User = new UserOptions() { };
cfg.User.RequireUniqueEmail = false;
cfg.SignIn.RequireConfirmedEmail = false;
})
.AddUserManager<AppUserManager<AppUser>>()
.AddUserStore<AppUserStore>()
.AddRoleStore<AppRoleStore>()
.AddDefaultTokenProviders();
services.Configure<TokenOptions>(Configuration.GetSection("TokenConf"));
var tokenConf = Configuration.GetSection("TokenConf").Get<TokenConf>();
services.Configure<AppConf>(Configuration.GetSection("AppConf"));
var appConf = Configuration.GetSection("AppConf").Get<AppConf>();
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidIssuer = tokenConf.Issuer,
ValidAudience = tokenConf.Audience,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(appConf.JWTSecretKey))
};
})
.AddCookie();
services.AddSingleton<IAuthenticationServiceProxy, AuthenticationServiceProxy>();
services.AddHttpContextAccessor();
services.AddMemoryCache();//alternatively we can use services.AddDistributedMemoryCache() and IDistributedCache cache
services.AddSession(options =>
{
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
services.AddOptions();
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizePage("/Index");
options.Conventions.AuthorizePage("/Privacy");
options.Conventions.AllowAnonymousToPage("/Account/Login");
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
This is my login code within LoginModel : PageModel
public async Task<IActionResult> OnPostAsync(string returnUrl, string handler)
{
if (!ModelState.IsValid)
{
return Page();
}
var appUser = new AppUser() { UserName = UserLogin.Username };
var result = await _signInMgr.PasswordSignInAsync(appUser, UserLogin.Password, false, false);//_signInMgr.PasswordSignInAsync(UserLogin.Username, UserLogin.Password, false, false);
if (result.Succeeded)
{
var userTokenData = _authServPrx.GetTokenData(_appConf.Value.CslLink, UserLogin.Username, UserLogin.Password);
JwtSecurityToken jwtSecurityToken = new JwtSecurityToken(userTokenData.Token);
var jwt = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
new OkObjectResult(jwt);
}
else
return BadRequest("Bad username or password"); //TODO: better return
return Page();
}
It doesn't work, the redirect doesn't happen, but somehow the authentication cookie does get set. So if I then manually go to /Index
I reach
public void OnGet()
{
var accessToken = Request.Headers["Authorization"];
}
But accessToken is empty.
I just want to redirect and to be able to access my token somehow.
Am I doing something wrong?
That depends on where you want to store the token ,session, cache.... For example, you can pass with query string :
if (result.Succeeded)
{
var userTokenData = _authServPrx.GetTokenData(_appConf.Value.CslLink, UserLogin.Username, UserLogin.Password);
JwtSecurityToken jwtSecurityToken = new JwtSecurityToken(userTokenData.Token);
var jwt = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
return LocalRedirect(returnUrl+"?token="+jwt);
}
And get token in page :
public void OnGet(string token)
{
}