Adding authentication in api endpoint - ASP.Net Core - asp.net

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();
}

Related

Using PKCE auth on swagger with an API that accepts bearer auth

I'm using Auth0 PKCE authentication on my NuxtJs Application which has a backing Dotnet 6 API. I've set up the API to accept a Bearer token as shown below. For ease of use, I wanted to set up my swagger environment to use PKCE auth also which I've done but the token never authenticates successfully to the Dotnet API. The access token I get from the UI and from postman successfully accessed the API because I am getting the access token and sending it as an authorization header. I attempted to also configure my web API to use OAuth2 along with JWT but the OAuth2 code is never getting hit.
I encountered the same issue originally in postman until I added my API Audience for the token to correctly generate but swagger doesn't seem to allow me to provide an audience.
How do I adjust swagger to use the PKCE auth but generate a token that works for the API?
Swagger setup:
private static void ConfigureSwaggerGen(SwaggerGenOptions options, IConfiguration config)
{
options.OperationFilter<SwaggerDefaultValues>();
options.UseDateOnlyTimeOnlyStringConverters();
var xmlFile = $"{typeof(Program).GetTypeInfo().Assembly.GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
options.IncludeXmlComments(xmlPath);
options.DocInclusionPredicate((docName, apiDesc) => apiDesc.RelativePath != null &&
!string.IsNullOrEmpty(apiDesc.GroupName) &&
apiDesc.RelativePath.Contains($"api/{docName}/"));
options.ExampleFilters();
options.TagActionsBy(api => new[]
{api.GroupName});
var oauthAuthority = config[$"{Constants.ConfigSettings}:{Constants.OAuthAuthority}"] ??
config[Constants.OAuthAuthority];
var securitySchema = new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
In = ParameterLocation.Header,
Flows = new OpenApiOAuthFlows
{
AuthorizationCode = new OpenApiOAuthFlow
{
AuthorizationUrl =
new Uri(
$"https://{oauthAuthority}/authorize"),
TokenUrl = new Uri($"https://{oauthAuthority}/oauth/token")
}
},
Description = Constants.SwaggerAuthDescription,
Name = Constants.SwaggerAuthName
};
options.OperationFilter<SecurityRequirementsOperationFilter>();
options.AddSecurityDefinition(Constants.SwaggerAuthName, securitySchema);
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{Id = Constants.SwaggerAuthName, Type = ReferenceType.SecurityScheme}
},
new List<string>()
}
});
}
private static void ConfigureSwaggerUi(SwaggerUIOptions options, IApiVersionDescriptionProvider provider,
IWebHostEnvironment env, IConfiguration config)
{
options.OAuthAppName("TestApp");
options.OAuthClientId(config[$"{Constants.ConfigSettings}:{Constants.OAuthClientId}"] ??
config[Constants.OAuthClientId]);
options.OAuthClientSecret(config[$"{Constants.ConfigSettings}:{Constants.OAuthClientSecret}"] ??
config[Constants.OAuthClientSecret]);
options.OAuthUsePkce();
options.OAuthScopes("profile", "openid", "api");
options.EnablePersistAuthorization();
options.OAuthConfigObject = new OAuthConfigObject
{
ClientId = config[$"{Constants.ConfigSettings}:{Constants.OAuthClientId}"] ??
config[Constants.OAuthClientId],
ClientSecret = config[$"{Constants.ConfigSettings}:{Constants.OAuthClientSecret}"] ??
config[Constants.OAuthClientSecret],
AppName = "TestApp",
Scopes = new List<string> {"profile", "openid", "api"},
AdditionalQueryStringParams = new Dictionary<string, string>
{
{
"audience", config[$"{Constants.ConfigSettings}:{Constants.OAuthAudience}"] ??
config[Constants.OAuthAudience]
}
},
UsePkceWithAuthorizationCodeGrant = true
};
if (env.IsProduction()) options.SupportedSubmitMethods();
foreach (var description in provider.ApiVersionDescriptions)
options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json",
description.GroupName.ToUpperInvariant());
}
Dotnet Auth Configuration:
private static void ConfigureAuth(IServiceCollection services, IConfiguration config)
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
var authority = config[$"{Constants.ConfigSettings}:{Constants.OAuthAuthority}"] ??
config[Constants.OAuthAuthority];
options.Authority = $"https://{authority}";
options.Audience = config[$"{Constants.ConfigSettings}:{Constants.OAuthAudience}"] ??
config[Constants.OAuthAudience];
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = config[$"{Constants.ConfigSettings}:{Constants.OAuthAudience}"] ??
config[Constants.OAuthAudience],
ValidIssuer = $"https://{authority}"
};
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
if (context.SecurityToken is not JwtSecurityToken token) return Task.CompletedTask;
if (context.Principal?.Identity is ClaimsIdentity identity)
identity.AddClaim(new Claim("access_token", token.RawData));
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
return Task.CompletedTask;
}
};
})
.AddOAuth("Auth0", options =>
{
var authority = config[$"{Constants.ConfigSettings}:{Constants.OAuthAuthority}"] ??
config[Constants.OAuthAuthority];
options.ClientId = config[$"{Constants.ConfigSettings}:{Constants.OAuthClientId}"] ??
config[Constants.OAuthClientId];
options.ClientSecret = config[$"{Constants.ConfigSettings}:{Constants.OAuthClientSecret}"] ??
config[Constants.OAuthClientSecret];
options.CallbackPath = new PathString("/auth/loggedin");
options.AuthorizationEndpoint = $"https://{authority}/authorize";
options.TokenEndpoint = $"https://{authority}/oauth/token";
options.UserInformationEndpoint = $"https://{authority}/userinfo";
options.Events = new OAuthEvents
{
OnCreatingTicket = async context =>
{
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
var response = await context.Backchannel.SendAsync(request,
HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
var user = await response.Content.ReadAsStringAsync();
var jUser = JsonSerializer.SerializeToElement(user);
context.RunClaimActions(jUser);
},
OnRedirectToAuthorizationEndpoint = context =>
{
context.Response.Redirect(context.RedirectUri);
return Task.CompletedTask;
},
OnTicketReceived = context =>
{
return Task.CompletedTask;
},
OnAccessDenied = context =>
{
return Task.CompletedTask;
},
OnRemoteFailure = context =>
{
return Task.CompletedTask;
}
};
});
}

Blazor Wasm HttpClient Authorisation header not providing claims

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);
}

.Net Core Refresh Token Not Working after Publish

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!

Identityserver4 Callback isuue

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.

.net core 2.2 Cannot retrieve JWT token from Request

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)
{
}

Resources