Implementing private_key_jwt and client_secret_jwt with Identity Server 4; providing client credentials using a JWT token - client

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.

Related

Can I change Identity Providers with OWIN and OpenID at run time?

I am using OWIN middleware to configure OpenID Authentication. This configuration is called at StartUp.cs points to a B2C IDP.
public void ConfigureAuth(IAppBuilder app)
{
// Required for Azure webapps, as by default they force TLS 1.2 and this project attempts 1.0
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
// ASP.NET web host compatible cookie manager
CookieManager = new SystemWebChunkingCookieManager()
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Generate the metadata address using the tenant and policy information
MetadataAddress = String.Format(Globals.WellKnownMetadata, Globals.Tenant, Globals.DefaultPolicy),
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = Globals.ClientId,
RedirectUri = Globals.RedirectUri,
PostLogoutRedirectUri = Globals.RedirectUri,
// Specify the callbacks for each type of notifications
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
},
// Specify the claim type that specifies the Name property.
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
ValidateIssuer = false
},
// Specify the scope by appending all of the scopes requested into one string (separated by a blank space)
Scope = $"openid profile offline_access {Globals.ReadTasksScope} {Globals.WriteTasksScope}",
// ASP.NET web host compatible cookie manager
CookieManager = new SystemWebCookieManager()
}
);
}
How can I get the middleware to use different configurations, specifically for the object OpenIdConnectAuthenticationOptions, in order to point to a different IDP at runtime?
You can register multiple named openIDCConnect handler like
.AddOpenIdConnect("Auth0", options =>
{ Options...
}
.AddOpenIdConnect("google", options =>
{ Options...
}
.AddOpenIdConnect("facebook", options =>
{ Options...
}
Then the user can choose how he wants to authenticate, using one of :
HttpContext.SignInAsync("Auth0",....);
HttpContext.SignInAsync("google",....);
HttpContext.SignInAsync("facebook",....);
When you add multiple handlers, you need to make sure the local callback path in the client is different for each handler, like
CallbackPath = new PathString("/signin-auth0");
CallbackPath = new PathString("/signin-google");
CallbackPath = new PathString("/signin-facebook");
(You set this in the options)

Ocelot Rate Limiting

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

jwt token in DOTNET WebApi

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

Is possible to protect scope (web api) and authenticate client (web app mvc) in same project?

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 .

Configure JWT Bearer token validation using the public security key in .NET Core

My web application is a kind of wrapper for some 3rd party service. This 3rd party service uses the JWT Bearer authentication to access its WebAPI endpoints. The tokens are encrypted with RS256 algorithm (asymmetric).
I have a Public Key to validate tokens signature on my side. It is easy to validate signature on jwt.io site (just paste the token and public key to the text boxes). But how do I configure TokenValidationParameters to have tokens validated automatically using specified Public Key?
AddAuthentication code snippet:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters.ValidateIssuer = true;
options.TokenValidationParameters.ValidIssuer = "iss";
options.TokenValidationParameters.ValidateIssuerSigningKey = true;
options.TokenValidationParameters.IssuerSigningKey = SomeCodeToGenerateSecurityKeyUsingPublicKeyOnly("-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----");
options.TokenValidationParameters.ValidateAudience = false;
options.TokenValidationParameters.ValidateLifetime = true;
options.TokenValidationParameters.ClockSkew = TimeSpan.Zero;
});
services.AddAuthorization(options =>
{
options.AddPolicy("Bearer",
new AuthorizationPolicyBuilder(new string[] { JwtBearerDefaults.AuthenticationScheme })
.RequireAuthenticatedUser()
.Build()
);
});
I can't just use SymmetricSecurityKey class like this:
options.TokenValidationParameters.IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("..."));
because of asymmetric encryption. In this case an exception occurs:
IDX10503: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey , KeyId:
'.
Exceptions caught:
''.
token: '{"alg":"RS256","typ":"JWT"}....
Okay, eventually I was succeeded with following solution. The RsaSecurityKey object do the trick I was looking for:
byte[] publicKeyBytes = Convert.FromBase64String("public_key_without_header_and_footer");
RsaKeyParameters rsaKeyParameters = (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes);
RSAParameters rsaParameters = new RSAParameters
{
Modulus = rsaKeyParameters.Modulus.ToByteArrayUnsigned(),
Exponent = rsaKeyParameters.Exponent.ToByteArrayUnsigned(),
};
......
options.TokenValidationParameters.IssuerSigningKey = new RsaSecurityKey(rsaParameters);
I'm not sure if this is the best solution, but I haven't some another one for now.

Resources