How to add attributes for custom claims types for IdP Saml2 response - itfoxtec-identity-saml2

I converted the IdPInitiatedController example code for my WebForms project. Although I don't get compiler or runtime error, I still cannot seem to get the manually created claims added to the Saml2AuthnResponse object after setting the claims identity. Is there another way to add attributes to the SAML response? The app uses forms authentication; and the credentials are in a SQL database. That's why I don't have Windows Identity.
' SSO CODE BEHIND
...
Dim binding = New Saml2PostBinding With {
.RelayState = String.Format("RPID={0}", Uri.EscapeDataString(serviceProviderRealm))
}
Dim config = New Saml2Configuration With {
.Issuer = "company.stage.app",
.SingleSignOnDestination = New Uri(ConfigurationManager.AppSettings.Get("appValue")),
.SigningCertificate = CertificateUtil.Load(X509Certificates.StoreName.TrustedPeople, X509Certificates.StoreLocation.LocalMachine, X509Certificates.X509FindType.FindByThumbprint, ConfigurationManager.AppSettings.Get("ThatKey")),
.SignatureAlgorithm = Saml2SecurityAlgorithms.RsaSha256Signature
}
Dim samlResponse = New Saml2AuthnResponse(config) With {
.Status = Saml2StatusCodes.Success
}
_ssoService.AddAttributes(samlResponse, currentUser)
binding.Bind(samlResponse) ' NO ATTRIBUTES WERE ADDED
Me.SAMLResponse.Value = binding.XmlDocument.InnerXml
Me.RelayState.Value = binding.RelayState
frmSSO.Action = config.SingleSignOnDestination.AbsoluteUri()
frmSSO.Method = "POST"
Public Sub AddAttributes(ByRef samlResponse As Saml2AuthnResponse, ByVal currentUser As CurrentUser)
Dim acctContact As CoveredInsuredDto = _coveragesService.GetAccountPointOfContact(currentUser.getControlNo())
If Not IsNothing(acctContact) Then
' Adding Attributes as claims
Dim claimIdentity = New ClaimsIdentity(Me.BuildClaims(currentUser, acctContact))
samlResponse.NameId = New IdentityModel.Tokens.Saml2NameIdentifier(acctContact.Name)
samlResponse.ClaimsIdentity = claimIdentity
End If
End Sub
Private Function BuildClaims(ByVal currentUser As CurrentUser, ByVal acctContact As CoveredInsuredDto) As IEnumerable(Of Claim)
Dim claims = New List(Of Claim)
If Not IsNothing(acctContact) Then
claims.Add(New Claim("FIRST_NAME", acctContact.FirstName))
claims.Add(New Claim("LAST_NAME", acctContact.LastName))
End If
Return claims.ToList()
End Function

The problem is that you have forgot the var token = response.CreateSecurityToken(appliesToAddress); line which is actually the line creating the token.
The ASP.NET Core IdP Initiated sample code:
public IActionResult Initiate()
{
var serviceProviderRealm = "https://some-domain.com/some-service-provider";
var binding = new Saml2PostBinding();
binding.RelayState = $"RPID={Uri.EscapeDataString(serviceProviderRealm)}";
var config = new Saml2Configuration();
config.Issuer = "http://some-domain.com/this-application";
config.SingleSignOnDestination = new Uri("https://test-adfs.itfoxtec.com/adfs/ls/");
config.SigningCertificate = CertificateUtil.Load(Startup.AppEnvironment.MapToPhysicalFilePath("itfoxtec.identity.saml2.testwebappcore_Certificate.pfx"), "!QAZ2wsx");
config.SignatureAlgorithm = Saml2SecurityAlgorithms.RsaSha256Signature;
var appliesToAddress = "https://test-adfs.itfoxtec.com/adfs/services/trust";
var response = new Saml2AuthnResponse(config);
response.Status = Saml2StatusCodes.Success;
var claimsIdentity = new ClaimsIdentity(CreateClaims());
response.NameId = new Saml2NameIdentifier(claimsIdentity.Claims.Where(c => c.Type == ClaimTypes.NameIdentifier).Select(c => c.Value).Single(), NameIdentifierFormats.Persistent);
response.ClaimsIdentity = claimsIdentity;
var token = response.CreateSecurityToken(appliesToAddress);
return binding.Bind(response).ToActionResult();
}
private IEnumerable<Claim> CreateClaims()
{
yield return new Claim(ClaimTypes.NameIdentifier, "some-user-identity");
yield return new Claim(ClaimTypes.Email, "some-user#domain.com");
}

Related

Validating an Azure AD generated JWT signature and algorithm in .NET Core 3.1

I am new to Azure AD. We are using v1.0 token. I have an Azure JWT token validation routine mostly based on ValidateSignature and AzureTokenValidation
The below is my ClaimsTransformer:
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
<do something>
var token = httpContextAccessor.HttpContext.Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
if (Validate(token))
{
<proceed to add claims>
}
and my validation routine:
public bool Validate(string token)
{
string stsEndpoint = "https://login.microsoftonline.com/common/.well-known/openid-configuration";
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsEndpoint, new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration config = configManager.GetConfigurationAsync().Result;
TokenValidationParameters parameters = new TokenValidationParameters
{
ValidIssuer = "https://sts.windows.net/<mytenant-id>",
ValidAudience = <my client-id>,
IssuerSigningKeys = config.SigningKeys,
ValidateAudience = true,
ValidateIssuer = true,
ValidateLifetime = true,
};
JwtSecurityTokenHandler tokendHandler = new JwtSecurityTokenHandler();
SecurityToken jwt;
bool verified;
try
{
var handler = tokendHandler.ValidateToken(token, validationParameters, out jwt);
var signature = ((System.IdentityModel.Tokens.Jwt.JwtSecurityToken)jwt).RawSignature;
string algorithm = ((System.IdentityModel.Tokens.Jwt.JwtSecurityToken)jwt).Header["alg"].ToString();
if (signature.Length == 0 || algorithm.ToLower().Equals("none"))
{
tokenVerified = false;
}
tokenVerified = true;
}
catch
{
tokenVerified = false;
}
return tokenVerified;
}
Please tell me if I am doing the right thing or can I just use (in my Validate(string token)
try
{
var result = tokendHandler.ValidateToken(token, validationParameters, out _);
verified = true;
}
catch {
verified = false;
}
and is, the checking for algorithm (to not accept "none" in alg) and signature presence is required or this way to check is right? There is no "secret keys"
JwtBearer middleware, like the OpenID Connect middleware in web apps, validates the token based on the value of TokenValidationParameters. The token is decrypted as needed, the claims are extracted, and the signature is verified. The middleware then validates the token by checking for this data:
Audience: The token is targeted for the web API.
Sub: It was issued for an app that's allowed to call the web API.
Issuer: It was issued by a trusted security token service (STS).
Expiry: Its lifetime is in range.
Signature: It wasn't tampered with.
For more details refer this document
Example:
String token = "Token";
string myTenant = "Tenant Name";
var myAudience = "Audience";
var myIssuer = String.Format(CultureInfo.InvariantCulture, "https://login.microsoftonline.com/{0}/v2.0", myTenant);
var mySecret = "Secrete";
var mySecurityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(mySecret));
var stsDiscoveryEndpoint = String.Format(CultureInfo.InvariantCulture, "https://login.microsoftonline.com/{0}/.well-known/openid-configuration", myTenant);
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever());
var config = await configManager.GetConfigurationAsync();
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidAudience = myAudience,
ValidIssuer = myIssuer,
IssuerSigningKeys = config.SigningKeys,
ValidateLifetime = false,
IssuerSigningKey = mySecurityKey
};
var validatedToken = (SecurityToken)new JwtSecurityToken();
// Throws an Exception as the token is invalid (expired, invalid-formatted, etc.)
tokenHandler.ValidateToken(token, validationParameters, out validatedToken);
Console.WriteLine(validatedToken);
Console.ReadLine();
OUTPUT

Manually create and validate a JWT token

I'm using IdentityServer4 Tools to manually create a token:
var token = await _tools.IssueClientJwtAsync(
clientId: "client_id",
lifetime: lifetimeInSeconds,
audiences: new[] { TokenHelper.Audience },
additionalClaims:new [] { new Claim("some_id", "1234") }
);
I wonder if there is a way (using what IdentityServer4 already have) to manually decode and validate the token.
To decode the token right now I'm using JwtSecurityTokenHandler (System.IdentityModel.Tokens.Jwt):
var handler = new JwtSecurityTokenHandler();
var tokenDecoded = handler.ReadJwtToken(token);
It is quite simple so I'm happy to keep this if IdentityServer4 doesn't have an equivalent.
What is more important is the validation of the token. I found and adapt this example that does the job. Here the code from Github:
const string auth0Domain = "https://jerrie.auth0.com/"; // Your Auth0 domain
const string auth0Audience = "https://rs256.test.api"; // Your API Identifier
const string testToken = ""; // Obtain a JWT to validate and put it in here
// Download the OIDC configuration which contains the JWKS
// NB!!: Downloading this takes time, so do not do it very time you need to validate a token, Try and do it only once in the lifetime
// of your application!!
IConfigurationManager<OpenIdConnectConfiguration> configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>($"{auth0Domain}.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration openIdConfig = AsyncHelper.RunSync(async () => await configurationManager.GetConfigurationAsync(CancellationToken.None));
// Configure the TokenValidationParameters. Assign the SigningKeys which were downloaded from Auth0.
// Also set the Issuer and Audience(s) to validate
TokenValidationParameters validationParameters =
new TokenValidationParameters
{
ValidIssuer = auth0Domain,
ValidAudiences = new[] { auth0Audience },
IssuerSigningKeys = openIdConfig.SigningKeys
};
// Now validate the token. If the token is not valid for any reason, an exception will be thrown by the method
SecurityToken validatedToken;
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
var user = handler.ValidateToken(testToken, validationParameters, out validatedToken);
// The ValidateToken method above will return a ClaimsPrincipal. Get the user ID from the NameIdentifier claim
// (The sub claim from the JWT will be translated to the NameIdentifier claim)
Console.WriteLine($"Token is validated. User Id {user.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value}");
The code above is doing the job. I just wonder if IdentityServer4 has already something "simpler" that just does the token validation as the code above does.
What you are trying to do is called token delegation,
you can implement it using Extension Grants on IDS. Here is sample code from docs
public class DelegationGrantValidator : IExtensionGrantValidator
{
private readonly ITokenValidator _validator;
public DelegationGrantValidator(ITokenValidator validator)
{
_validator = validator;
}
public string GrantType => "delegation";
public async Task ValidateAsync(ExtensionGrantValidationContext context)
{
var userToken = context.Request.Raw.Get("token");
if (string.IsNullOrEmpty(userToken))
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
return;
}
var result = await _validator.ValidateAccessTokenAsync(userToken);
if (result.IsError)
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
return;
}
// get user's identity
var sub = result.Claims.FirstOrDefault(c => c.Type == "sub").Value;
//Generate a new token manually if needed
//Call another API is needed
context.Result = new GrantValidationResult(sub, GrantType);
return;
}
}
Token validation is done using ITokenValidator in above code, you can use this validator in manual validation as well.
Here is another example.

Multiple certificates for Issuer ITfoxtec.Identity.Saml2

As context: I am trying to implement SAML2.0 authentication using ITfoxtec.Identity.Saml2 library. I want to use multiple certificates for one Service Provider, because different clients could login to Service Provider and each of them can have its own certificate. I need a third-party login service have possibility to choose among the list of certificates from my Service Provider metadata.xml when SAML request happened. Does ITfoxtec.Identity.Saml2 library support this possibility or are there some workarounds how it can be implemented?. Thank You
You would normally have one Saml2Configuration. But in your case I would implement some Saml2Configuration logic, where I can ask for a specific Saml2Configuration with the current certificate (SigningCertificate/DecryptionCertificate). This specific Saml2Configuration is then used in the AuthController.
The metadata (MetadataController) would then call the Saml2Configuration logic to get a list of all the certificates.
Something like this:
public class MetadataController : Controller
{
private readonly Saml2Configuration config;
private readonly Saml2ConfigurationLogic saml2ConfigurationLogic;
public MetadataController(IOptions<Saml2Configuration> configAccessor, Saml2ConfigurationLogic saml2ConfigurationLogic)
{
config = configAccessor.Value;
this.saml2ConfigurationLogic = saml2ConfigurationLogic;
}
public IActionResult Index()
{
var defaultSite = new Uri($"{Request.Scheme}://{Request.Host.ToUriComponent()}/");
var entityDescriptor = new EntityDescriptor(config);
entityDescriptor.ValidUntil = 365;
entityDescriptor.SPSsoDescriptor = new SPSsoDescriptor
{
WantAssertionsSigned = true,
SigningCertificates = saml2ConfigurationLogic.GetAllSigningCertificates(),
//EncryptionCertificates = saml2ConfigurationLogic.GetAllEncryptionCertificates(),
SingleLogoutServices = new SingleLogoutService[]
{
new SingleLogoutService { Binding = ProtocolBindings.HttpPost, Location = new Uri(defaultSite, "Auth/SingleLogout"), ResponseLocation = new Uri(defaultSite, "Auth/LoggedOut") }
},
NameIDFormats = new Uri[] { NameIdentifierFormats.X509SubjectName },
AssertionConsumerServices = new AssertionConsumerService[]
{
new AssertionConsumerService { Binding = ProtocolBindings.HttpPost, Location = new Uri(defaultSite, "Auth/AssertionConsumerService") }
},
AttributeConsumingServices = new AttributeConsumingService[]
{
new AttributeConsumingService { ServiceName = new ServiceName("Some SP", "en"), RequestedAttributes = CreateRequestedAttributes() }
},
};
entityDescriptor.ContactPerson = new ContactPerson(ContactTypes.Administrative)
{
Company = "Some Company",
GivenName = "Some Given Name",
SurName = "Some Sur Name",
EmailAddress = "some#some-domain.com",
TelephoneNumber = "11111111",
};
return new Saml2Metadata(entityDescriptor).CreateMetadata().ToActionResult();
}
private IEnumerable<RequestedAttribute> CreateRequestedAttributes()
{
yield return new RequestedAttribute("urn:oid:2.5.4.4");
yield return new RequestedAttribute("urn:oid:2.5.4.3", false);
}
}

TokenCache: No matching token was found in the cache, Azure AD Api

I'd like to use Azure AD Api and I couldn't acquire token some reason. I have two methods, and I got this after calling:
TokenCache: No matching token was found in the cache iisexpress.exe Information: 0
Here's my code:
public string GetToken()
{
string authority = "https://login.microsoftonline.com/{tenantId}/";
string clientId = "";
string secret = "";
string resource = "https://graph.windows.net/";
var credential = new ClientCredential(clientId, secret);
AuthenticationContext authContext = new AuthenticationContext(authority);
//I think the problem is here:
var token = authContext.AcquireTokenAsync(resource, credential).Result.AccessToken;
return token;
}
public string MakeRequest()
{
string accessToken = GetToken();
var tenantId = "";
string graphResourceId = "https://graph.windows.net/";
Uri servicePointUri = new Uri(graphResourceId);
Uri serviceRoot = new Uri(servicePointUri, tenantId);
ActiveDirectoryClient client = new ActiveDirectoryClient(serviceRoot, async () => await Task.FromResult(accessToken));
foreach (var user in client.Users.ExecuteAsync().Result.CurrentPage)
Console.WriteLine(user.DisplayName);
var client1 = new HttpClient();
var uri = "https://graph.windows.net/" + tenantId + "/users?api-version=1.6";
client1.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
var response = client1.GetAsync(uri).Result;
var result = response.Content.ReadAsStringAsync().Result;
return result;
}
I don't know what's the problem, and I didn't find any great hint, under other questions and a little explanation would be helpful. I'd like to understand this part, of course.
//When you are calling
Main() { Method_A() }
aync Method_A() { await Method_B() }
Task < T > Method_B() { return T; }
//It will through the error. //Need to keep Mehtod_B in another Task and run.
// Here I am avoiding few asyncs
Main() { Method_A() }
Method_A() { Method_B().Wait() }
Task Method_B() { return T; }
There is no output using the Console.WriteLine in a IIS progress. If you want to output the result in a output window for the web project, you can use System.Diagnostics.Debug.WriteLine() method.

Web API 2, return string as a file

I have a Web API 2 POST endpoint which takes a parameter, queries the database and returns an xml string as the response.
public async Task<IHttpActionResult> Post(long groupId)
{
People people = await _someService.GetPeople(groupId);
XElement peopleXml = _xmlService.ConverToXml(people);
return Ok(peopleXml);
}
How do I to return the xml as a file instead?
Figured it out myself, but I hope there is a simpler way -
public async Task<IHttpActionResult> Post(long groupId)
{
People people = await _someService.GetPeople(groupId);
XElement peopleXml = _xmlService.ConverToXml(people);
byte[] toBytes = Encoding.Unicode.GetBytes(peopleXml.ToString());
var stream = new MemoryStream(toBytes);
var result = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(stream)
};
result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileName = "test.txt"
};
result.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
var response = ResponseMessage(result);
return response;
}

Resources