Following is my code to register authentication
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
ValidateIssuer = true,
ValidIssuer = issuer,
ValidateAudience = true,
ValidAudience = audience,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = tokenValidationParameters;
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
Console.WriteLine("OnAuthenticationFailed: " + context.Exception.Message);
Trace.WriteLine("OnAuthenticationFailed: " + context.Exception.Message);
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
Console.WriteLine("OnTokenValidated: " + context.SecurityToken);
Trace.WriteLine("OnTokenValidated: " + context.SecurityToken);
return Task.CompletedTask;
}
};
});
Following is my controller code
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
[Authorize]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
But my request to api/values never makes it to this controller.
Somehow the request completes in OnTokenValidated event.
I get 401 response.
What am I doing wrong here?
To my understanding, when using Identity the defaultAuthenticateScheme is set to cookie authentication.
I am unsure what options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; is supposed to do but it didn't change the DefaultAuthenticateScheme or DefaultChallengeScheme when I tested it in my program that is using JWT authentication with identity.
try adding in
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
and then to prevent getting a 404 instead of a 401 when not authorized add
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
if you want to use cookie authentication with JWT you can set the DefaultAuthenticatieScheme in the [Authorize] tag like so either:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
Related
I have build a Teams application on Asp.net core and connected the oauth backend provider. At startup.cs, I dont know the Team's culture yet. Then I ask user to login and get the culture in my cshtml page. But I am not able to find a way to pass Team's cultutre to my oauth backend.The settings goes like this in statup.cs:
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieScheme;
options.DefaultChallengeScheme = OAuthScheme;
options.DefaultAuthenticateScheme = CookieScheme;
options.DefaultSignInScheme = CookieScheme;
options.DefaultSignOutScheme = CookieScheme;
})
.AddCookie(CookieScheme, options =>
{
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.None;
options.ExpireTimeSpan = TimeSpan.FromMinutes(120);
options.SlidingExpiration = false;
options.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = RefreshTokenIfRequired
};
})
.AddOAuth(OAuthScheme, options =>
{
var settings = JsonConvert.DeserializeObject<OnlineApiSettings>(Configuration["settings"]);
options.ClientId = settings.ClientId;
options.ClientSecret = settings.ClientSecret;
options.AuthorizationEndpoint = settings.AuthEndpoint;
options.TokenEndpoint = settings.TokenEndpoint;
options.CallbackPath = new PathString("/oauth/callback");
options.SaveTokens = true;
});
This is how login page constructed:
<script type="text/javascript">
function loginV1() {
microsoftTeams.app.initialize().then(() => {
microsoftTeams.app.getContext().then((context) => {
login(window.location.origin + "/LoginPopup?culture=" + context.app.locale);
});
});
}
function login(url) {
const urlParams = new URLSearchParams(window.location.search);
const redirectUrl = urlParams.get('redirectPath');
microsoftTeams.authentication.authenticate({
url: url,
width: 600,
height: 535,
successCallback: function (result) {
console.log("Login succeeded: " + result);
window.location.href = "/" + redirectUrl;
},
failureCallback: function (reason) {
console.log("Login failed: " + reason);
window.location.href = "/" + redirectUrl;
}
});
}
</script>
and LoginPopup page is like below:
[Authorize]
public class LoginPopupModel : PageModel
{
private readonly IHttpContextAccessor _httpContext;
public LoginPopupModel(IHttpContextAccessor httpContextAccessor)
{
_httpContext = httpContextAccessor;
}
public async Task<IActionResult> OnGetAsync()
{
if (_httpContext.HttpContext.User.Identity.IsAuthenticated)
{
return RedirectToPage("LoginSuccess");
}
return null;
}
}
I have created a ASP.NET WEB.API project with OWIN JWT Authentication. Is is working as expected, but what I want is to show custom validation message to use instead of default message if JWT authentication fails.
Right now it shows
{
"message": "Authorization has been denied for this request."
}
This is my Validation Code.
public class OwinStartUp
{
public void Configuration(IAppBuilder app)
{
HttpStatusCode statusCode;
HttpResponseMessage message = new HttpResponseMessage();
try
{
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "http://jwt.com",
ValidAudience = "http://jwt.com",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("jwt_key_asdf"))
}
});
}
catch (SecurityTokenValidationException e )
{
statusCode = HttpStatusCode.Unauthorized;
message = new HttpResponseMessage(HttpStatusCode.Unauthorized)
{
Content = new StringContent("Failed") /////// Even tried this but it does not work.
};
}
catch(Exception ex)
{
}
}
}
I'm new at the Jwt. I create new web api running on 3.1 version and my configuration like that ;
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
var symmetricKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("mysupersecretkeytosharewithnooneandalwaysinsideapp"));
TokenValidationParameters tokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = "Management",
ValidateIssuer = true,
ValidAudience = "Management",
ValidateAudience = true,
ValidateLifetime = true,
IssuerSigningKey = symmetricKey,
ValidateIssuerSigningKey = true,
};
services.AddCors();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = tokenValidationParameters;
});
services.AddHttpContextAccessor();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors(q => q.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
And my token generator class like that ;
public static string GenerateToken(string jwtKey, string jwtExpireMinutes)
{
var claims = new List<Claim>()
{
new Claim(ClaimTypes.NameIdentifier,"C280298D-D896-49E2-96AD-5C82243F9048"),
new Claim(ClaimTypes.Email,"mfa#gmail.com"),
};
var keyByte = Encoding.UTF8.GetBytes(jwtKey);
var signInKey = new SymmetricSecurityKey(keyByte);
var expireMinutes = DateTime.Now.AddMinutes(Convert.ToDouble(jwtExpireMinutes));
var token = new JwtSecurityToken(claims: claims, expires: expireMinutes, signingCredentials: new SigningCredentials(signInKey, SecurityAlgorithms.HmacSha256));
return new JwtSecurityTokenHandler().WriteToken(token);
}
I tried on Postman and I'm getting token successfuly but with this token When I call my method it returns 401 unauthorized.
Controller has these attributes ;
[ApiController]
[Route("api/[controller]")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
These are the results ;
That is because you check the aud (audience) and iss (issuer) claim in your jwt token when validating token :
ValidIssuer = "Management",
ValidateIssuer = true,
ValidAudience = "Management",
ValidateAudience = true,
So that when creating token you should adding these correct claims ,otherwise token validation won't pass :
var token = new JwtSecurityToken(audience: "Management", issuer: "Management", claims: claims, expires: expireMinutes, signingCredentials: new SigningCredentials(signInKey, SecurityAlgorithms.HmacSha256));
Last week I am trying to configure the IdentityServer4 to get an access token automatically updating.
I had an API:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5100";
options.RequireHttpsMetadata = false;
options.ApiName = "api1";
});
My MVC client configuration:
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "http://localhost:5100";
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.ClientSecret = "secret";
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("api1");
options.Scope.Add("offline_access");
});
And the IdentityServer's clients configuration:
return new List<Client>
{
new Client
{
ClientId = "mvc",
ClientName = "My mvc",
AllowedGrantTypes = GrantTypes.Hybrid,
RequireConsent = false,
AccessTokenLifetime = 10,
ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = { "http://localhost:5102/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:5102/signout-callback-oidc" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess,
"api1"
},
AllowOfflineAccess = true
}
};
On the client side I use AJAX queries to call the API to get/post/put/delete data. I add the access token to the request and get the result.
private async getAuthenticationHeader(): Promise<any> {
return axios.get('/token').then((response: any) => {
return { headers: { Authorization: `Bearer ${response.data}` } };
});
}
async getAsync<T>(url: string): Promise<T> {
return this.httpClient
.get(url, await this.getAuthenticationHeader())
.then((response: any) => response.data as T)
.catch((err: Error) => {
console.error(err);
throw err;
});
}
The access token is provided by the MVC client method:
[HttpGet("token")]
public async Task<string> GetAccessTokenAsync()
{
return await HttpContext.GetTokenAsync("access_token");
}
It works fine. After access token expired I get 401 on the client side, so it would be great to have an opportunity to update access token automatically when it was expired.
According to a documentation I supposed, that It can be reached by setting AllowOfflineAccess to true and adding suitable scope "offline_access".
Maybe I don't understand the right flow of the access and refresh tokens usages. Can I do it automatically or it is impossible? I suppose, that we can use refresh tokens in out queries, but I don't understand how.
I've read a lot of SO answers and github issues but I am still confused. Could you help me to figure out?
After investigation and communicating in comments I've found the answer. Before every API call I get the expite time and according to the result update access_token or return existing:
[HttpGet("config/accesstoken")]
public async Task<string> GetOrUpdateAccessTokenAsync()
{
var accessToken = await HttpContext.GetTokenAsync("access_token");
var expiredDate = DateTime.Parse(await HttpContext.GetTokenAsync("expires_at"), null, DateTimeStyles.RoundtripKind);
if (!((expiredDate - DateTime.Now).TotalMinutes < 1))
{
return accessToken;
}
lock (LockObject)
{
if (_expiredAt.HasValue && !((_expiredAt.Value - DateTime.Now).TotalMinutes < 1))
{
return accessToken;
}
var response = DiscoveryClient.GetAsync(_identitySettings.Authority).Result;
if (response.IsError)
{
throw new Exception(response.Error);
}
var tokenClient = new TokenClient(response.TokenEndpoint, _identitySettings.Id, _identitySettings.Secret);
var refreshToken = HttpContext.GetTokenAsync("refresh_token").Result;
var tokenResult = tokenClient.RequestRefreshTokenAsync(refreshToken).Result;
if (tokenResult.IsError)
{
throw new Exception();
}
accessToken = tokenResult.AccessToken;
var idToken = HttpContext.GetTokenAsync("id_token").Result;
var tokens = new List<AuthenticationToken>
{
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.IdToken,
Value = idToken
},
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.AccessToken,
Value = accessToken
},
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.RefreshToken,
Value = tokenResult.RefreshToken
}
};
var expiredAt = DateTime.UtcNow.AddSeconds(tokenResult.ExpiresIn);
tokens.Add(new AuthenticationToken
{
Name = "expires_at",
Value = expiredAt.ToString("o", CultureInfo.InvariantCulture)
});
var info = HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme).Result;
info.Properties.StoreTokens(tokens);
HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, info.Principal, info.Properties).Wait();
_expiredAt = expiredAt.ToLocalTime();
}
return accessToken;
}
}
I call this method to get the access_token and add int to the API call headers:
private async getAuthenticationHeader(): Promise<any> {
return axios.get('config/accesstoken').then((response: any) => {
return { headers: { Authorization: `Bearer ${response.data}` } };
});
}
async getAsync<T>(url: string): Promise<T> {
return this.axios
.get(url, await this.getAuthenticationHeader())
.then((response: any) => response.data as T)
.catch((err: Error) => {
console.error(err);
throw err;
});
}
Double check locking were implemented to prevent simultamious async API calls try to change access_token at the same time. Optionally you can cashe you access_token into static variable or cache, it is up to you.
If you have any advices or alternatives it would be insteresting to discuss. Hope it helps someone.
There's 2 ways of doing this:
Client side - Handle the authentication and obtaining of the token on the client side using a lib like oidc-client-js. This has a feature that allows automatic renewal of the token via a prompt=none call to the authorize endpoint behind the scenes.
Refresh token - store this in your existing cookie and then use it to request a new access token as needed. In this mode your client side code doing the AJAX calls would need to be aware of token errors and automatically request a new token from the server whereby GetAccessTokenAsync() could use the refresh token to get a new access token.
I'm trying to add JWT validation in my dot net core application. I've followed this link to understand JWT and able to generate a token by givings some values like this.
var token = new JwtSecurityToken(
issuer: issuer,
audience: aud,
claims: claims,
expires: expTime,
signingCredentials: creds
);
Edit: and to follow this answer, I've also added JwtBearerAuthentication middleware in my app by adding app.UseJwtBearerAuthentication(new JwtBearerOptions { /* options */ }) to Startup.Configure() method.
Now I'm stuck how could I pass this token inside HTTP header? I'm generating this token on Login but whats next? How could I get to know that JWT is added and working fine??
Any kind of help will be appreciated.
This is a runnable sample for bearer token authentication in ASP.NET Core.
How to achieve a bearer token authentication and authorization in ASP.NET Core
At back end, you can generate the token following this code:
[Route("api/[controller]")]
public class TokenAuthController : Controller
{
[HttpPost]
public string GetAuthToken(User user)
{
var existUser = UserStorage.Users.FirstOrDefault(u => u.Username == user.Username && u.Password == user.Password);
if (existUser != null)
{
var requestAt = DateTime.Now;
var expiresIn = requestAt + TokenAuthOption.ExpiresSpan;
var token = GenerateToken(existUser, expiresIn);
return JsonConvert.SerializeObject(new {
stateCode = 1,
requertAt = requestAt,
expiresIn = TokenAuthOption.ExpiresSpan.TotalSeconds,
accessToken = token
});
}
else
{
return JsonConvert.SerializeObject(new { stateCode = -1, errors = "Username or password is invalid" });
}
}
private string GenerateToken(User user, DateTime expires)
{
var handler = new JwtSecurityTokenHandler();
ClaimsIdentity identity = new ClaimsIdentity(
new GenericIdentity(user.Username, "TokenAuth"),
new[] {
new Claim("ID", user.ID.ToString())
}
);
var securityToken = handler.CreateToken(new SecurityTokenDescriptor
{
Issuer = TokenAuthOption.Issuer,
Audience = TokenAuthOption.Audience,
SigningCredentials = TokenAuthOption.SigningCredentials,
Subject = identity,
Expires = expires
});
return handler.WriteToken(securityToken);
}
}
In Startup.cs/ConfigureServices method
services.AddAuthorization(auth =>
{
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser().Build());
});
And add this code in Configure method
app.UseJwtBearerAuthentication(new JwtBearerOptions {
TokenValidationParameters = new TokenValidationParameters {
IssuerSigningKey = TokenAuthOption.Key,
ValidAudience = TokenAuthOption.Audience,
ValidIssuer = TokenAuthOption.Issuer,
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(0)
}
});
At front end, you just add the token to header like this:
$.ajaxSetup({
headers: { "Authorization": "Bearer " + accessToken }
});
or
$.ajax("http://somedomain/somepath/somepage",{
headers:{ "Authorization": "Bearer " + accessToken },
/*some else parameter for ajax, see more you can review the Jquery API*/
});