This question already has answers here:
JWT Token authentication, expired tokens still working, .net core Web Api
(4 answers)
Closed 1 year ago.
I am trying to implement Token Based Authentication through refresh tokens and JWT in .NET Core 2.1.
This is how I am implementing the JWT Token:
Startup.cs
services.AddAuthentication(option =>
{
option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
option.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = Configuration["Jwt:Site"],
ValidIssuer = Configuration["Jwt:Site"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:SigningKey"]))
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
});
Token Generation:
var jwt = new JwtSecurityToken(
issuer: _configuration["Jwt:Site"],
audience: _configuration["Jwt:Site"],
expires: DateTime.UtcNow.AddMinutes(1),
signingCredentials: new SigningCredentials(signinKey, SecurityAlgorithms.HmacSha256)
);
return new TokenReturnViewModel()
{
token = new JwtSecurityTokenHandler().WriteToken(jwt),
expiration = jwt.ValidTo,
currentTime = DateTime.UtcNow
};
I am getting he correct values in Response.
But after a minute I set the same token for authorization in Postman and it works.
If the token has expired it shouldn't.
I am using bearer tokens as authentication.
What am I doing wrong? Need direction.
There is a token validation parameter called ClockSkew, it gets or sets the clock skew to apply when validating a time. The default value of ClockSkew is 5 minutes. That means if you haven't set it, your token will be still valid for up to 5 minutes.
If you want to expire your token on the exact time; you'd need to set ClockSkew to zero as follows,
options.TokenValidationParameters = new TokenValidationParameters()
{
//other settings
ClockSkew = TimeSpan.Zero
};
Another way, create custom AuthorizationFilter and check it manually.
var principal = ApiTokenHelper.GetPrincipalFromToken(token);
var expClaim = principal.Claims.First(x => x.Type == "exp").Value;
var tokenExpiryTime = Convert.ToDouble(expClaim).UnixTimeStampToDateTime();
if (tokenExpiryTime < DateTime.UtcNow)
{
//return token expired
}
Here, GetPrincipalFromToken is a custom method of the ApiTokenHelper class, and it will return the ClaimsPrincipal value that you've stored while issuing a token.
Related
I've been using Ocelot lately to build an API Gateway. Are rate limits based on the requester client id? Because i've been asked to build an api gateway in an architecture that will look like this
And all the requests will have the same id since they are passing through the proxy.
I can however, identify the different requesting clients using an header token. So my question is: can i limit the number of request made by a client using the header token rather than the request id? Thanks in advance.
You could use Ocelot as a Rate Limiter based on ClientId.
"RateLimitOptions": {
"DisableRateLimitHeaders": false,
"QuotaExceededMessage": "Customize Tips!",
"HttpStatusCode": 999,
"ClientIdHeader" : "MY-CLIENT-ID"
}
The last line in Ocelot's rate limiting documentation refers to this:
ClientIdHeader - Allows you to specifiy the header that should be used to identify clients. By default it is “ClientId”
You could also implement your own middleware and use Ocelots rate limitng. So You could be able to read other Headers and get your customized client-id:
Just take a look at default rate limiting middleware provided by Ocelot: ClientRateLimitMiddleware.cs
public virtual ClientRequestIdentity SetIdentity(HttpContext httpContext, RateLimitOptions option)
{
var clientId = "client";
if (httpContext.Request.Headers.Keys.Contains(option.ClientIdHeader))
{
clientId = httpContext.Request.Headers[option.ClientIdHeader].First();
}
return new ClientRequestIdentity(
clientId,
httpContext.Request.Path.ToString().ToLowerInvariant(),
httpContext.Request.Method.ToLowerInvariant()
);
}
An easy and good solution for me was to add the "ClientId" in the header as soon as I got the JWT token validation.
// Authentication
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = builder.Configuration.GetSection("OAuthSettings:Authority").Get<string>();
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudiences = builder.Configuration.GetSection("OAuthSettings:Audiences").Get<string>().Split(';')
};
options.RequireHttpsMetadata = false;
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
// Adding the ClientId in the header
// It is used by the rate limiting
string clientId = context.Principal.Claims.FirstOrDefault(x => x.Type == JwtClaimTypes.ClientId).Value;
context.HttpContext.Request.Headers.Add("ClientId", clientId);
return Task.CompletedTask;
},
};
});
I literally read the internet through, tried multiple approaches and so on - with no luck.
I am trying to make a pretty small WebApi project with a few controllers protected by a jwt token.
I generate the token using following code:
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("may the force"));
var tokenHandler = new JwtSecurityTokenHandler();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims.ToArray()),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
I have setup the authentication (in Startup.cs) bits like
services.AddAuthentication(x => {
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}
)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("may the force"))
};
});
Using my AuthController I can generate the Token(first code block in this question) - and the Controllers needed to be "protected" is annotated with [Authorize]
If I take the result from AuthController and paste it at https://jwt.io the signature is valid.
I am testing the controllers using postman and setting the request header Authorization to Bearer <the token>
Can anyone point me in the right direction?
Thanks alot!
UPDATE: I misused the jwt.io site initially, thus some of the comments underneath here are no longer valid.
UPDATE 2: Realized that the server tried to use cookie based authentication. Changing the annotation to [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] made the trick - but still not sure why
I have web api project (ASP.Net 4.7) where i am Jwt Bearer Token for authentication and i would like to hook to an event when token is successfully validated call user information endpoint and add custom logic and add claims to identity.
I know we can do something similar with dotnet core https://www.jerriepelser.com/blog/aspnetcore-jwt-saving-bearer-token-as-claim/
Asp.Net 4.x
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(issuer + "/.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever(), new HttpDocumentRetriever());
var discoveryDocument = Task.Run(() => configurationManager.GetConfigurationAsync()).GetAwaiter().GetResult();
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
TokenValidationParameters = new TokenValidationParameters()
{
ValidAudience = "Any",
ValidIssuer = issuer,
IssuerSigningKeyResolver = (token, securityToken, identifier, parameters) =>
{
return discoveryDocument.SigningKeys;
}
},
});
Please advise ?
Is it possible to implement windows and jwt authentication schemes in same project?
I need windows authentication to catch user without any login page and jwt to handle roles with any other page and wep api.
Yes, you can add multiple Authentication schemes to your application. Refer to the following link
I finally got the both working. I didn't find anything solved example on internet, hopefully this would help anyone looking for answers.
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IISDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "Bearer";
}).AddJwtBearer("Bearer", options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
//ValidAudience = "the audience you want to validate",
ValidateIssuer = false,
//ValidIssuer = "the isser you want to validate",
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("myapisecretkey")),
ValidateLifetime = true, //validate the expiration and not before values in the token
ClockSkew = TimeSpan.FromMinutes(5) //5 minute tolerance for the expiration date
};
});
services.AddAuthorization(auth =>
{
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireClaim(ClaimTypes.Name, "MyAPIUser").Build());
});
Then select the authentication scheme you want to use on particular controller by decorating it.
[Route("api/MyController")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class MyController : Controller
I am using Identity server 4(with entity-framework for configs) and defining a MVC client with reference token (AccessTokenType=1). I can login to IS4 by using the client and defined user and get access token (reference type). I know that this token does not contains claims but I have all claims in Security.Claims.ClaimPrincipal. Is it getting claims by doing behind the scene request to IS4?
I have 2 main issues:
1) I set the access token life time to 10 mins for MVC client, and cookie is valid for 450 hours. I expect that after 10 mins user redirected to login page on IS4 as access token is expired but it is not happening
2) Also when I remove PersistedGrants from database, still I am logged in and can see MVC client, Why?
Should I do anything in middleware on MVC client to check access token by using reference token?
I need this for forcing user to login on all logged in clients again.
this my MVC setting:
app.UseCookieAuthentication(
new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies",
AutomaticAuthenticate = true,
ExpireTimeSpan = TimeSpan.FromHours(750),
AutomaticChallenge = true
});
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
app.UseOpenIdConnectAuthentication(
new OpenIdConnectOptions
{
AuthenticationScheme = "oidc",
SignInScheme = "Cookies",
Authority = "http://localhost:7010",
RequireHttpsMetadata = false,
ClientId = "MVC_Client",
ClientSecret = "MVC_Client",
ResponseType = "code id_token",
Scope =
{
Common.Constants.IdentityManagement.OpenIdScopeName,
Common.Constants.IdentityManagement.ProfileScopeName,
Common.Constants.IdentityManagement.EmailScopeName,
},
GetClaimsFromUserInfoEndpoint = true,
SaveTokens = true,
TokenValidationParameters =
new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role
},
Events = new OpenIdConnectEvents()
{
OnTicketReceived = OnTicketReceived
}
});
The client has two grant types: hybrid,client_credentials
And this is client properties in databse:
[AbsoluteRefreshTokenLifetime]: 60
[AccessTokenLifetime]: 60
[AccessTokenType]: 1
[AllowAccessTokensViaBrowser]: False
[AllowOfflineAccess]: True
[AllowPlainTextPkce]: False
[AllowRememberConsent]: True
[AlwaysIncludeUserClaimsInIdToken]: False
[AlwaysSendClientClaims]: False
[AuthorizationCodeLifetime]: 60
[ClientId]: MVC_Client
[ClientName]: MVC_Client
[ClientUri]: NULL
[EnableLocalLogin]: True
[Enabled]: True
[IdentityTokenLifetime]: 60
[IncludeJwtId]: False
[LogoUri]: NULL
[LogoutSessionRequired]: True
[LogoutUri]: NULL
[PrefixClientClaims]: True
[ProtocolType]: oidc
[RefreshTokenExpiration]: 60
[RefreshTokenUsage]: 1
[RequireClientSecret]: True
[RequireConsent]: False
[RequirePkce]: False
[SlidingRefreshTokenLifetime]: 60
[UpdateAccessTokenClaimsOnRefresh]: False
I've solved the similar issue and I set the life time of the cookie according the life time of access token and after refreshing of access token it will be renew the cookie.
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
...
SaveTokens = true,
UseTokenLifetime = false,
Events = new OpenIdConnectEvents()
{
OnTicketReceived = n => OnTicketReceived(n)
}
});
private Task OnTicketReceived(TicketReceivedContext n)
{
var accessTokenExpiresAt = n.Properties.Items[".Token.expires_at"];
n.Properties.ExpiresUtc = DateTimeOffset.Parse(accessTokenExpiresAt);
return Task.FromResult(0);
}
Another way how you can manage the cookie is used the following method OnValidatePrincipal:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = LastChangedValidator.ValidateAsync
}
});
public static class LastChangedValidator
{
public static async Task ValidateAsync(CookieValidatePrincipalContext context)
{
// Check if user is still valid
if (!isUserValid)
{
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(
CookieAuthenticationDefaults.AuthenticationScheme);
}
}
}
The following code is from here: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?tabs=aspnetcore1x
Thanks Jenan.
Set cookie expiry to token expiry works.
Another issue I have is that when I remove the reference tokens from PersistedGrants and after 10 mins (access token lifetime) the cookie is expired and I expect that user get redirected to login page. But MVC client goes to IS and IS creates new reference token without authentication. How it is possible?
In the IdentityServer log I see this:
[Microsoft.AspNetCore.Hosting.Internal.WebHost] Request starting HTTP/1.1 GET http://localhost:7010/connect/authorize?client_id=IdentityManagement&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fsignin-oidc&response_type=code%20id_token&scope=openid%20profile%20email%20Roles%20IdentityManagement%20IdentityUsers&response_mode=form_post&nonce=636452284146749770.ZTk3YzFiM2QtYTQxMi00MGI3LWJjMGEtMWFkOGRhYTE0ZjE3NDhjZGE3MjMtYTczNS00Y2ZkLThhOTctNzAxYmM4NTY4MjE5&state=CfDJ8HzK9L_BsbZJtObgOKdlRawJPSBTZc1UETnT9osu2OIOojB6vxT7t7GjIBO2nf2TYngPk3u8EcDMk8o_dVUvj8VTaEQf0s1DvTUwaxZn93_TKv1waoFukeEBFwaSB1yWTbNq62dyYkLc6_fkiW4r16BwFyKpVEvaMmh2NGLUmfFiQ-7qj6f4VyR3pM0ydd7Ah8Vs6BIfXlyqtQJ4Ak4sR1jrcGO9-ViTWCFe2YN0M9-OWFluiFQOylh4quzwseYjjOgY0ruVCwK7Lw1pvVMewnn_f2uiXk7QXBz7TMYcp8kylbdgL5Vx0fSBrB67nKSER5m-gjPXNIky6FrBPSouqzw
[Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware] HttpContext.User merged via AutomaticAuthentication from authenticationScheme: "idsrv".
[Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware] AuthenticationScheme: "idsrv" was successfully authenticated.
[IdentityServer4.Hosting.IdentityServerMiddleware] Invoking IdentityServer endpoint: "IdentityServer4.Endpoints.AuthorizeEndpoint" for "/connect/authorize"
[Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware] AuthenticationScheme: "idsrv" was successfully authenticated.
Why IdentityServer creates new reference token without authentication. The previous reference token is removed from PersistedGrants.
I am doing this for forcing user to login again in case of emergency (losing device,...)
Correct, I used this for set the IS cookie:
var props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.AddSeconds(120)
};
await HttpContext.Authentication.SignInAsync(userCredential.User.Id.ToString(), userCredential.User.UserName, props);
But I do not want to set it the same as client cookie expiry as it affects SSO. Also if I set the IS cookie greater than client cookie the other issue happens (as IS cookie is valid IS creates new reference token without authentication).
Now I am confused!