How SAML Assertion/verification works with X509 public key - c#

In my asp.net MVC web-site, I have implemented SSO in which the IDP/ADFS sends the SAML response and I verify the SAML token to allow users to access the web-site. I am using customized code (using System.IdentityModel.dll and System.IdentityModel.Services.dll libraries to verify the SAML response and token, instead of having.net framework do the checking with web.config settings).
So far the code seems to be working fine, but since I am new to this domain have concern that hacker would able to bypass the validation with properly constructed SAML response. Recently I tried my hands on the SAML token generation part and I realized that my token verification code can be bypassed if the token is generated intelligently.
At high level this is what I am doing to verify the token:
Extract the security token from the request.
Verify the token is valid and untouched by checking the digest value with the provided X509 public key(which is present in the SAML response)
Extract the claims/identity of the user and allow the access.
My concern is that if hacker creates SAML token (like my own token generator code) and add public key in the response and post it to my web site, my web site will successfully validate the response as the response itself is well formed and signed. Is this a valid concern? I am missing some basic validations to handle this scenario?
I can think of following options to mitigate the risk:
Check the URLReferrer and make sure that the SAML response is posted from the expected entity. I am not sure if there is a way to manipulate the URLReferrer.
Avoid using the public key present in the response to validate the digest value. I can store the X509 certificate on my end and use it to validate the response. The hacker won’t able to sign the response with the same certificate as he won’t have the private key. If this is the right way, can someone suggest me how to instruct “tokenhandler” to ignore the public key present in the response and use explicit public key?
Is there a way by which I can make back-end call to IDP, May a web-service call, and check that the token received by my web site is indeed generated by the IDP?
Since I am a newbie, I might be missing the basic SAML validation concept. So please let me know if my concern is legitimate.
Here is the sample code I use to validate the response.
public ActionResult SAMLAssert()
{
var fam = new WSFederationAuthenticationModule();
var request = this.HttpContext.Request;
// Get the security token from the SAML response
var securityToken = fam.GetSecurityToken(request);
var config = new SecurityTokenHandlerConfiguration
{
CertificateValidator = X509CertificateValidator.None,
IssuerNameRegistry = new CustomIssuerNameRegistry(),
};
config.AudienceRestriction.AudienceMode = AudienceUriMode.Never;
var tokenHandler = new Saml2SecurityTokenHandler
{
CertificateValidator = X509CertificateValidator.None,
Configuration = config,
};
//// validate the token and get the ClaimsIdentity out of it
var identity = tokenHandler.ValidateToken(securityToken);
bool isSuccess = identity[0].IsAuthenticated;
// Code to retrieve the claims/user information from the token
//....
return View();
}
And here is the custom "IssuerNameRegistry".
public class CustomIssuerNameRegistry : IssuerNameRegistry
{
public override string GetIssuerName(SecurityToken securityToken)
{
X509SecurityToken x509Token = securityToken as X509SecurityToken;
return x509Token.Certificate.Subject;
}
}
I have suspicion that the custom class is the problematic part as it’s not doing any validation.

I don't think you should check the referrer value. It can easily be spoofed.
The IdP uses its private key to sign responses sending to you. An attacker just doesn't have access to this private key. Therefore, if an attacker wants to spoof a signature, he will need to use his own certificate and put his public key to the token. While you are right that validation code uses the embedded public key for verifying signature, AFAICT it also does one more thing: check if the public key is trusted by our machine. The trust here can be established by adding the public key to your Windows Certificate store -> TrustedPeople. I don't have all the code to verify this though but it should work this way, or it should provide you a way to do that.
If you have full control over the IdP, alternative to embedded public key (aka X509Data), you can use keyname, subject name, thumbprint only. But how much more secure they provide and how to implement are out of the scope of this question.
No, SAML protocol wasn't designed to work that way. The closest thing to this is to use Artifact flow in which the IdP only returns an Artifact to your application and it needs to make an ArtifactResolve request to the IdP to get the actual Response. See What is the purpose of a SAML Artifact?. But then you still need to verify signature of the received Response.

Related

How to adapt the custom code section in the PowerApps connector config to allow a OAuth 2.0 resource owner password credentials Grant type

I am trying to create a custom connection in PowerApps that calls a REST API which requires oauth2 authentication with the password credentials Grant type. I have set up the connection to the API successfully in Postman using Grant Type: Password Credentials and sending the relevant username and password. However, when I try to create the connection in Powerapps there are no fields for 'Grant Type' or to send the username and password. This appears to be a known limitation of Powerapps.
As a workaround, I was wondering whether the custom code field in the custom connector setup could be adapted to send the relevant credentials along with the existing request data, given that the documentation states that Custom code transforms request and response payloads beyond the scope of policy templates.
Here is the boiler plate custom code in C#
public class Script : ScriptBase
{
public override async Task<HttpResponseMessage> ExecuteAsync()
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = CreateJsonContent("{\"message\": \"Hello World\"}");
return response;
}
}
Does anyone know how to adapt this to send hard coded key value pairs, along the lines of {"Grant Type" : "Password Credentials"} and {"UserName" : "Foo", "Password" : "Bar"} in the request payload, alongside existing information that is sent?

How to make SignalR Bearer Authentication Secure OR how to Not Log the Query String

When using Bearer Token Authentication, SignalR passes the JWT token through the query string. Obviously this bears the risk that the the token gets logged along with the request and thus anybody who can read the log can impersonate by copying the token.
The docs say:
If you have concerns about logging this data with your server logs, you can disable this logging entirely by configuring the Microsoft.AspNetCore.Hosting logger to the Warning level or above (these messages are written at Info level)
At this stage a side question pops into my mind: who guarantees that if the log level is Warning and something bad happens, the log won't still contain the request URL?
The docs continue with:
If you still want to log certain request information, you can write a middleware to log the data you require and filter out the access_token query string value (if present).
I guess the idea is to entirely switch off the default logging of requests and replace it with a custom logging middleware. This does not sound trivial to me. So I was wondering:
Is there any way of hooking into the logger and then customize what's actually being logged?
Or can we leverage the existing HTTP Logging for that? At the first glance it also seems to be a all-or-nothing option in regards to logging of query strings or is there a way of customizing that?
Is there a NuGet package that solves the issue?
How did others solve this problem?
I've resorted to take an approach where the JWT token does need to be sent as part of the query string, as explained here.
To summarize, when set as a cookie, the cookie will automatically be sent as part of the SignalR connection initialization by the browser:
document.cookie = `X-Authorization=${token}; path=/; secure; samesite=strict`; // https://stackoverflow.com/a/48618910/331281
const newConnection = new HubConnectionBuilder()
.withUrl('/background-queue-hub', {
skipNegotiation: true, // to avoid CORS problems (see: https://stackoverflow.com/a/52913505/331281)
transport: HttpTransportType.WebSockets,
})
...
However, this runs the risk of CSWSH. So, server-side we have to check the origin header to mitigate that. It can be done right where the cookie value is copied to the JWT Bearer authentication context:
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options => // https://stackoverflow.com/a/66485247/331281
{
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
// for SignalR authentication we need to read the access token form the cookie
// since we don't want to pass the authentication token through the query string
// as the query string is being logged
if (context.HttpContext.Request.Path.StartsWithSegments(SignalRHubPath))
{
var allowedOrigins = Configuration["SignalR:AllowedOrigins"].Split(';');
if (allowedOrigins.Contains(context.Request.Headers["Origin"].ToString())) // see: https://www.tpeczek.com/2017/07/preventing-cross-site-websocket.html
{
context.Token = context.Request.Cookies["X-Authorization"];
}
else
{
context.Response.StatusCode = StatusCodes.Status403Forbidden;
}
}
return Task.CompletedTask;
}
};
});

Custom Token Service for issuing and validating JWT

We need to develop a "single sign on" service (SSO) to issue JWT for numerous amount of clients. Developers of these clients will also need an ability to validate these tokens. Obviously we can't provide them with our secret key we used to generate these tokens. So instead we decided to provide them an API service with two methods. One for issuing token and the second one to validate it.
I'm questioning myself if we're going for the right approach. Here is a basic scheme which shows how users will be working with their clients (secure applications)
User signs on with his credentials via our service and gets his access token.
Then his token is used in request headers of the secure application. SSO client module is AuthenticationHandler which sends HTTP requests to our service to check validity of the token.
Here is some code from SSO client module we use to validate the token. We use custom authentication handler which makes calls to the remote SSO service:
internal class SsoAuthenticationHandler : AuthenticationHandler<SsoAuthenticationOptions>
{
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!TryRetrieveToken(Request, out var token))
{
return AuthenticateResult.NoResult();
}
if (await _ssoClient.ValidateTokenAsync(token))
{
return AuthenticateResult.Success(...);
}
return AuthenticateResult.NoResult();
}
}
and the SsoClient iself:
public class SsoClient
{
public async Task<bool> ValidateTokenAsync(string token)
{
const string validateUrl = "api/auth/validatetoken";
var address = $"https://{_ssoHost}/{validateUrl}";
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
var res = await httpClient.GetStringAsync(new Uri(address));
reply = DeserializeSsoReply(res);
}
return reply.Succeeded;
}
}
I couldn't find what would be best practices for our scenario so I wonder if there are any possible pitfalls we can encounter with this approach?
When we had a similar situation what we did was for each valid client they had a certificate they could use to create JWTs for the destination (aka us) service. After validating at their origin, their credentials were repackaged and resigned using the certificate we shared with them. For a limited number of valid callers this helped us keep track of the true origin (based on the signing cert) as well as having a standardized payload for our use.
On receipt we validate that the audience is correct (aka us) and that signer is one of the configured signers.
Everyone keeps their own secrets and anyone you want to believe that you are you, you provide your public key. The owner of the key (aka you) always controls your secret but everyone has to agree on the end to end protocol.
I don't consider this reinventing so much as securing the popular path and limiting entry to a know set of users.

Exchanging a google idToken for local openId token c#

I am using this github project https://github.com/openiddict/openiddict-core which is great. But I am stuck as to what the procedures should be, or how to implement them, when the user uses an external identity provider, for this example, I will use google.
I have an angular2 app running, with an aspnet core webAPI. All my local logins work perfectly, I call connect/token with a username and password, and an accessToken is returned.
Now I need to implement google as an external identity provider. I have followed all the steps here to implement a google login button. This opens a popup when the user logins in. This is the code I have created for my google button.
// Angular hook that allows for interaction with elements inserted by the
// rendering of a view.
ngAfterViewInit() {
// check if the google client id is in the pages meta tags
if (document.querySelector("meta[name='google-signin-client_id']")) {
// Converts the Google login button stub to an actual button.
gapi.signin2.render(
'google-login-button',
{
"onSuccess": this.onGoogleLoginSuccess,
"scope": "profile",
"theme": "dark"
});
}
}
onGoogleLoginSuccess(loggedInUser) {
let idToken = loggedInUser.getAuthResponse().id_token;
// here i can pass the idToken up to my server and validate it
}
Now I have an idToken from google. The next step on the google pages found here says that I need to validate the google accessToken, which I can do, but how do I exchange the accessToken that I have from google, and create local accessToken which can be used on my application?
Edit: this answer was updated to use OpenIddict 3.x.
The next step on the google pages found here says that i need to validate the google accessToken, which i can do, but how do i exchange the accessToken that i have from google, and create local accessToken which can be used on my application?
The flow you're trying to implement is known as assertion grant. You can read this other SO post for more information about it.
OpenIddict fully supports custom grants, so this is something you can easily implement in your token endpoint action:
[HttpPost("~/connect/token"), Produces("application/json")]
public IActionResult Exchange()
{
var request = HttpContext.GetOpenIddictServerRequest();
if (request.GrantType == "urn:ietf:params:oauth:grant-type:google_identity_token")
{
// Reject the request if the "assertion" parameter is missing.
if (string.IsNullOrEmpty(request.Assertion))
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidRequest,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
"The mandatory 'assertion' parameter was missing."
}));
}
// Create a new ClaimsIdentity containing the claims that
// will be used to create an id_token and/or an access token.
var identity = new ClaimsIdentity(TokenValidationParameters.DefaultAuthenticationType);
// Manually validate the identity token issued by Google, including the
// issuer, the signature and the audience. Then, copy the claims you need
// to the "identity" instance and call SetDestinations on each claim to
// allow them to be persisted to either access or identity tokens (or both).
//
// Note: the identity MUST contain a "sub" claim containing the user ID.
var principal = new ClaimsPrincipal(identity);
foreach (var claim in principal.Claims)
{
claim.SetDestinations(claim.Type switch
{
"name" => new[]
{
Destinations.AccessToken,
Destinations.IdentityToken
},
_ => new[] { Destinations.AccessToken },
});
}
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
throw new InvalidOperationException("The specified grant type is not supported.");
}
Note that you'll also have to enable it in the OpenIddict server options:
services.AddOpenIddict()
// ...
.AddServer(options =>
{
// ...
options.AllowCustomFlow("urn:ietf:params:oauth:grant-type:google_identity_token");
});
When sending a token request, make sure to use the right grant_type and to send your id_token as the assertion parameter, and it should work. Here's an example with Postman (for Facebook access tokens, but it works exactly the same way):
That said, you have to be extremely careful when implementing the token validation routine, as this step is particularly error-prone. It's really important to validate everything, including the audience (otherwise, your server would be vulnerable to confused deputy attacks).

Validating Google ID tokens in C#

I need to validate a Google ID token passed from a mobile device at my ASP.NET web api.
Google have some sample code here but it relies on a JWT NuGet package which is .Net 4.5 only (I am using C#/.Net 4.0). Is anyone aware of any samples which do this without these packages or has achieved this themselves? The use of the package makes it very difficult to work out what I need to do without it.
According to this github issue, you can now use GoogleJsonWebSignature.ValidateAsync method to validate a Google-signed JWT. Simply pass the idToken string to the method.
var validPayload = await GoogleJsonWebSignature.ValidateAsync(idToken);
Assert.NotNull(validPayload);
If it is not a valid one, it will return null.
Note that to use this method, you need to install Google.Apis.Auth nuget firsthand.
The challenge is validating the JWT certificate in the ID token. There is currently not a library I'm aware of that can do this that doesn't require .Net 4.5 and until there is a solution for JWT validation in .NET 4.0, there will not be an easy solution.
However, if you have an access token, you can look into performing validation using oauth2.tokeninfo. To perform basic validation using token info, you can do something like the following:
// Use Tokeninfo to validate the user and the client.
var tokeninfo_request = new Oauth2Service().Tokeninfo();
tokeninfo_request.Access_token = _authState.AccessToken;
var tokeninfo = tokeninfo_request.Fetch();
if (userid == tokeninfo.User_id
&& tokeninfo.Issued_to == CLIENT_ID)
{
// Basic validation succeeded
}
else
{
// The credentials did not match.
}
The information returned from the Google OAuth2 API tells you more information about a particular token such as the client id it was issued too as well as its expiration time.
Note You should not be passing around the access token but instead should be doing this check after exchanging a one-time code to retrieve an access token.
ClientId also needs to be passed, which should be set from Google API Console. If only pass TokenId, GoogleJsonWebSignature throws error. This answer is in addition to #edmundpie answer
var settings = new GoogleJsonWebSignature.ValidationSettings()
{
Audience = new List<string>() { "[Placeholder for Client Id].apps.googleusercontent.com" }
};
var validPayload = await GoogleJsonWebSignature.ValidateAsync(model.ExternalTokenId, settings);

Categories

Resources