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;
},
};
});
Related
Good morning,
I need to have in same project both web api and web app mvc.
Web api has to be protected via bearer token and web app mvc has to be authenticated via identity server.
Is it possible protecting a scope and a client in same project?
I think I have to do something like this in startup
//this to protect scope api1
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "http://localhost:5000/";
options.RequireHttpsMetadata = false;
options.Audience = "api1";
});
//this to authenticate mvc client
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies", options =>
{
options.AccessDeniedPath = "/account/denied";
})
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "http://localhost:5000",
options.RequireHttpsMetadata = false;
options.ResponseType = "id_token token";
options.ClientId = "mvc-implicit";
options.SaveTokens = true;
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("api1");
options.GetClaimsFromUserInfoEndpoint = true;
options.ClaimActions.MapJsonKey("role", "role", "role");
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
};
});
Now, I have to call my Api1 using client_credential with an external client.
But it returns me at login page.
Is it possible to do what I want?
Protected WebApi and Authenticated MVC client in same project?
Now, I have to call my Api1 using client_credential with an external client. But it returns me at login page.
That seems you misunderstand the scenario . Your MVC application is client also is a resource application which protected by Identity Server (in Config.cs):
public static IEnumerable<ApiResource> GetApis()
{
return new List<ApiResource>
{
new ApiResource("api1", "My API")
};
}
I assume you have api controller in your MVC application :
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET: api/Values
[HttpGet]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
And you have config to protect the api actions by using AddJwtBearer :
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "http://localhost:5000/";
options.RequireHttpsMetadata = false;
options.Audience = "api1";
});
That means any request to access the Get action should have an authentication bearer header with access token append , the access token is issued by your Identity Server(endpoint is http://localhost:5000/) and the audience is api1 .
Now your another client could use client credential flow to acquire access token to access your web application :
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000");
if (disco.IsError)
{
Console.WriteLine(disco.Error);
return;
}
// request token
var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "client",
ClientSecret = "secret",
Scope = "api1"
});
And call your protected actions :
var apiClient = new HttpClient();
apiClient.SetBearerToken(tokenResponse.AccessToken);
var response = await apiClient.GetAsync("http://localhost:64146/api/values");
if (!response.IsSuccessStatusCode)
{
Console.WriteLine(response.StatusCode);
}
else
{
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(JArray.Parse(content));
}
So it won't redirect to login page , since client credential in fact is sending HTTP POST request to get access token with app's credential . There is no login page in this scenario .
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.
Using Identity Server 4, how do you hook into the exchange between the client and server when using the authorization_code flow to provide Client credentials to the Identity Server using a JWT Token?
Below is the solution:
In ConfigureServices the is key to hook into the Identity Server pipeline and provide a call-back for the event OnAuthorizationCodeReceived. This event is called at the point in the pipeline where the authorization code is received back from Identity server during the normal exchange between the client and server as described by https://www.ietf.org/rfc/rfc6750.txt.
Doing this give you the opportunity to create the JWT token and make it available from that point on in the pipeline.
Configuration on the client
services.AddAuthentication(options =>
...
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.RemoteAuthenticationTimeout = TimeSpan.FromMinutes(10);
options.UseTokenLifetime = false;
options.RequireHttpsMetadata = false;
options.Authority = "http://localhost:44320/";
options.ClientId = "cliend-id";
options.ClientSecret = "client-secret";
options.ResponseType = "code";
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.Events.OnAuthorizationCodeReceived = delegate (AuthorizationCodeReceivedContext context)
{
var clientassertion = new ClientAssertion("client-id", "http://localhost:44320/connect/token");
var assertion = clientassertion.CreateJwtClientAssertionAsymmetric("localhost");
context.TokenEndpointRequest.ClientAssertion = assertion.ClientAssertion;
context.TokenEndpointRequest.ClientAssertionType = assertion.ClientAssertionType;
return Task.CompletedTask;
};
...
Configuration on the server
As http://docs.identityserver.io/en/release/topics/secrets.html?highlight=beyond indicates under the section beyond shared secrets.
The important bit here is to ensure the type and value are aligned as in the example below.
var client = new Client
{
...
ClientSecrets =
{
new Secret
{
Type = IdentityServerConstants.SecretTypes.X509CertificateBase64,
Value = "MIIDATCC..."
}
},
AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
AllowedScopes = { "api1", "api2" }
};
Implementation
Implement the interfaces ISecretParser and ISecretValidator
Then add to implementations to the DI system in ConfigureServices.
Eg.
builder.AddSecretParser()
builder.AddSecretValidator()
If your Validator is not getting called, ensure RequireClientSecret is set to true.
Take the appropriate action in the parser, and validator (from the parse return success for failure).
This approach work for both private_key_jwt and client_secret_jwt.
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 develop two separated applications: MVC and WebAPI. On some pages of MVC application I perform ajax requests to WebAPI. Furthermore, I use IdentityServer3 as an authentication/authorization framework.
I've already implemented cookie-based authentication for MVC part and token-based for WebAPI basing on tutorials/samples published on GitHub. Each of them works as intended, but user has to log in twice (separately in MVC and WebAPI), which seems to be reasonable because I've used different authentication types.
Is it possible to use IdentityServer3 in a way that user is required to log in once? I'm wondering if it's a good idea to generate access token by MVC app (after cookie-based authorization) and provide it to JavaScript part of application (the token would be used during ajax calls). I think that this solution allows to avoid double signing in. I've read a lot of posts about similar problems, but they haven't given unambiguous answer.
Edit:
I've followed Paul Taylor's suggestion to use "Hybrid Flow" and I've found a couple of samples which illustrate how to implement it (among other things this tutorial), but I cannot figure out how to perform valid ajax requests to WebAPI. Currently, I get 401 Unauthorized error, though HTTP header Authorization: Bearer <access token> is set for all ajax requests.
IdentityServer project
Scopes:
var scopes = new List<Scope>
{
StandardScopes.OfflineAccess,
new Scope
{
Enabled = true,
Name = "roles",
Type = ScopeType.Identity,
Claims = new List<ScopeClaim>
{
new ScopeClaim(IdentityServer3.Core.Constants.ClaimTypes.Role, true)
}
},
new Scope
{
Enabled = true,
DisplayName = "Web API",
Name = "api",
ScopeSecrets = new List<Secret>
{
new Secret("secret".Sha256())
},
Claims = new List<ScopeClaim>
{
new ScopeClaim(IdentityServer3.Core.Constants.ClaimTypes.Role, true)
},
Type = ScopeType.Resource
}
};
scopes.AddRange(StandardScopes.All);
Client:
new Client
{
ClientName = "MVC Client",
ClientId = "mvc",
Flow = Flows.Hybrid,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = new List<string>
{
Constants.StandardScopes.OpenId,
Constants.StandardScopes.Profile,
Constants.StandardScopes.Email,
Constants.StandardScopes.Roles,
Constants.StandardScopes.Address,
Constants.StandardScopes.OfflineAccess,
"api"
},
RequireConsent = false,
AllowRememberConsent = true,
AccessTokenType = AccessTokenType.Reference,
RedirectUris = new List<string>
{
"http://localhost:48197/"
},
PostLogoutRedirectUris = new List<string>
{
"http://localhost:48197/"
},
AllowAccessTokensViaBrowser = true
}
MVC application project
Startup configuration
const string AuthorityUri = "https://localhost:44311/identity";
public void Configuration(IAppBuilder app)
{
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = "mvc",
Authority = AuthorityUri,
RedirectUri = "http://localhost:48197/",
ResponseType = "code id_token",
Scope = "openid profile email roles api offline_access",
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
},
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n =>
{
var tokenClient = new TokenClient(AuthorityUri + "/connect/token", "mvc", "secret");
TokenResponse tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, n.RedirectUri);
if (tokenResponse.IsError)
throw new Exception(tokenResponse.Error);
UserInfoClient userInfoClient = new UserInfoClient(AuthorityUri + "/connect/userinfo");
UserInfoResponse userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken);
ClaimsIdentity id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
id.AddClaims(userInfoResponse.Claims);
id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
id.AddClaim(new Claim("sid", n.AuthenticationTicket.Identity.FindFirst("sid").Value));
n.AuthenticationTicket = new AuthenticationTicket(
new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, "name", "role"),
n.AuthenticationTicket.Properties);
},
RedirectToIdentityProvider = n => { // more code }
}
});
}
After I receive access token, I store it in the sessionStorage.
#model IEnumerable<System.Security.Claims.Claim>
<script>
sessionStorage.accessToken = '#Model.First(c => c.Type == "access_token").Value';
</script>
Following JavaScript function is used to perform ajax requests:
function ajaxRequest(requestType, url, parameters)
{
var headers = {};
if (sessionStorage.accessToken) {
headers['Authorization'] = 'Bearer ' + sessionStorage.accessToken;
}
$.ajax({
url: url,
method: requestType,
dataType: 'json',
data: parameters,
headers: headers
});
}
WebAPI project
Startup configuration:
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "https://localhost:44311/identity",
ClientId = "mvc",
ClientSecret = "secret",
RequiredScopes = new[] { "api", "roles" }
});
Could you tell me what I'm doing wrong?
Edit (solved)
I had invalid configuration of WebAPI because nomenclature is misleading. It turned out that ClientId and ClientSecret should contian name of scope and its secret (link to reported issue).
Following Startup configuration of WebAPI works as intended:
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "https://localhost:44311/identity",
// It has been changed:
ClientId = "api", // Scope name
ClientSecret = "secret", // Scope secret
RequiredScopes = new[] { "api", "roles" }
});
You need to use IdentityServer3's "Hybrid Flow".
Here's a tutorial on how to implement it with IdentityServer3. https://identityserver.github.io/Documentation/docsv2/overview/mvcGettingStarted.html
This page for an explanation of how the Hybrid Flow works, and how to implement it (using IdentityServer4 - which unlike IdentityServer3, is still actively developed in case you have the option to upgrade). http://docs.identityserver.io/en/release/quickstarts/5_hybrid_and_api_access.html.