How to get an already issued SAML assertion? - c#

I have a scenario where I have an ASP.Net application that authenticates using the Thinktecture IdentityServer. This all works fine, it has a relationship with our ADFS and that is all working great. What I need though is to call the ShareFile-NET SDK and authenticate using the below sample code..
//SAML Authentication: This authentication support assumes you have a mechanism for obtaining a SAML assertion, samlAssertion from the user's IdP.
var sfClient = new ShareFileClient("https://secure.sf-api.com/sf/v3/");
var oauthService = new OAuthService(sfClient, "[clientid]", "[clientSecret]");
var oauthToken = await oauthService.ExchangeSamlAssertionAsync(samlAssertion,
subdomain, applicationControlPlane);
sfClient.AddOAuthCredentials(oauthToken);
sfClient.BaseUri = oauthToken.GetUri();
So I have the IdP, but I have not had any luck researching how exactly to make use of the token it has provided me to create that "samlAssertion" parameter..

I have found the answer to this.
The SAML assertion can be found in the ClaimsIdentity
var icp = System.Security.Claims.ClaimsPrincipal.Current;
var claimsIdentity = icp.Identity as System.Security.Claims.ClaimsIdentity;
var token = claimsIdentity.BootstrapContext as System.IdentityModel.Tokens.BootstrapContext;
For this to be populated you need to add the following to the web.config:
<identityConfiguration saveBootstrapContext="true">

Related

Authenticate Azure AD using Provided Access Token

I tried to find an Azure authentication mechanism that uses an access token as a parameter. but no luck. I just found ClientSecretCredential class that use tenant id, client id, and client secret as a parameter like below :
var clientSecretCredential = new ClientSecretCredential(tenantId, clientId, clientSecret);
the reason I need the that is the access token will be generated by another service and my service will only accept access token to be used to authenticate Azure AD.
Actually, I can utilize Azure Management RestAPI to do that. However to improve developer experience I'd like to utilize .NET client library if possible.
I have tried to find documentation in Azure Identity client library in https://learn.microsoft.com/en-us/dotnet/api/overview/azure/identity-readme?view=azure-dotnet, but I couldn't find any class or method that I need.
If you want tokens from azure ad you can use using Microsoft.IdentityModel.Clients.ActiveDirectory; library to get the tokens.
I am assuming that you have already created an azure ad app registration and already possess the client_id , client_secret and tenant_id. Just save this as strings in code.
Now we can use the clientcredential along with authenticationContext we can acquire tokens.
Complete program :
string client_id = "";
string client_secret = "";
string tenant_id = "";
string endpoint = "https://login.microsoftonline.com/"+tenant_id;
ClientCredential credent = new ClientCredential(client_id , client_secret);
var context = new AuthenticationContext(endpoint);
var result = context.AcquireTokenAsync("https://management.azure.com/",credent);
var result = context.AcquireTokenAsync("https://management.azure.com/",credent);
Console.WriteLine(result.Result.AccessToken);
Here result.Result.AccessToken will give you a access token in form of a token.

Shared session cookie is not being recognised by authentication scheme

This follows on from a previous post which started as a general issue and is now more specific.
In short, I've been following guidance (such as this from Microsoft, this from Scott Hanselman, and this from Barry Dorrans) to allow me to share the authentication cookie issued by a legacy ASP.NET web app with a new dotnet core app running on the same domain.
I'm confident that I'm using the recommended Microsoft.Owin.Security.Interop library correctly. On that side (the old ASP.NET app), the CookieAuthenticationOptions are configured with AuthenticationType and CookieName both set to the same value - SiteIdentity. This same value is also used in the interop data protector setup:
var appName = "SiteIdentity";
var encryptionSettings = new AuthenticatedEncryptorConfiguration
{
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
};
var interopProvider = DataProtectionProvider.Create(
new DirectoryInfo(keyRingSharePath),
builder =>
{
builder.SetApplicationName(appName);
builder.SetDefaultKeyLifetime(TimeSpan.FromDays(365 * 20));
builder.UseCryptographicAlgorithms(encryptionSettings);
if (!generateNewKey)
{
builder.DisableAutomaticKeyGeneration();
}
});
ShimmedDataProtector = new DataProtectorShim(
interopProvider.CreateProtector(
"Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware",
appName,
"v2"));
I log in using this app, confirm I have a cookie named SiteIdentity then switch to a new dotnet core app running on the same domain.
There, without adding authentication middleware I can confirm that I can unprotect and deserialize the cookie. I do this by setting up data protection in Startup to match the other app:
var appName = "SiteIdentity";
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(keyRingSharePath))
.SetDefaultKeyLifetime(TimeSpan.FromDays(365 * 20))
.DisableAutomaticKeyGeneration()
.UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration()
{
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
})
.SetApplicationName(appName);
Then in my controller I can use a data protector to manually unprotect the cookie:
var appName = "SiteIdentity";
var protector = _dataProtectionProvider.CreateProtector(
"Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware",
appName,
"v2");
var cookieValue = Request.Cookies[appName];
var format = new TicketDataFormat(protector);
var ticket = format.Unprotect(cookieValue);
I can confirm that ticket.Principal does indeed reference a claims principal representing the account which I signed in with on the other app.
However, I've found it impossible to wire up the cookie authentication middleware to properly protect my endpoints using this cookie. This is what I've added to Startup, after the data protection code above:
var protectionProvider = services.BuildServiceProvider().GetService<IDataProtectionProvider>();
var dataProtector = protectionProvider.CreateProtector(
"Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware",
appName,
"v2");
services
.AddAuthentication(appName)
.AddCookie(appName, options =>
{
options.TicketDataFormat = new TicketDataFormat(dataProtector);
options.Cookie.Name = appName;
});
By my understanding this is telling the middleware that I have an authentication scheme named "SiteIdentity" (the advice is that authentication scheme must match the ASP.NET authentication type) which expects a cookie also called "SiteIdentity" which will contain protected data that the supplied data protector can interpret.
But when I add the attribute [Authorize(AuthenticationSchemes = "SiteIdentity")] to my controller I'm kicked away to a login page.
I can't understand what I'm doing wrong. As I've shown, I can confirm that it is indeed possible to use this data protector and ticket format to interpret the authentication cookie, so I guess I must have something wrong in this middleware wiring, but I'm not sure what.
Please ignore. It turns out that my code is actually correct. I had been working on this solution for long enough that the session represented by the cookie value I was using to test had expireed. Will leave this question here in case the code benefits anyone trying to achieve the same.

IdentityServer Tutorial , token has Invalid Signature

I created a test application with the identity server.
It is very simple. it has some hard coded InMemory Users,Clients and SCopes and uses the idsrv3test.pfx certificated from the samples for signing
var factory = new IdentityServerServiceFactory();
factory
.UseInMemoryUsers(MemoryUsers.All())
.UseInMemoryClients(MemoryUsers.GetClients())
.UseInMemoryScopes(MemoryUsers.GetScopes());
var cert = new X509Certificate2(#"..\certs\idsrv3test.pfx", "idsrv3test");
var options = new IdentityServerOptions()
{
Factory = factory,
EnableWelcomePage = true,
SigningCertificate = cert,
RequireSsl = false
};
app.UseIdentityServer(options);
Now I get a a token via the connect/token endpoint. as grant type I use password.
This succeeds and I got a bearer token back.
now I wanted to validated the token contents on jwt.io . I shows me all the informations of all parts of the token. but at the end of the site it shows me "invalid signature"
Is this the result of a bug ? Or just a result that I use this test certificate?
Jwt.io cannot validate RS256 signatures. Only HS256.

AdminSettings API using service account in a C# Console application

I'm trying to use the Google Admin Settings API with a Service Account with no success from a C# Console application.
From what I've understood, I first have to get an OAuth token. I've tried 2 methods successfully for this: using Google.Apis.Auth.OAuth2.ServiceAccountCredentials or by creating manually the JWT assertion.
But when I call an Admin Settings API with the OAuth token (maximumNumberOfUsers for instance), I always get a 403 error with " You are not authorized to perform operations on the domain xxx" message.
I downloaded GAM as the author calls this API too so that I can compose the same HTTP requests. Like explained in GAM wiki, I followed all the steps to create a new Service Account and a new OAuth Client ID so that I can be sure it's not a scope issue. I also activated the debug mode like proposed by Jay Lee in this thread. Like explained in the thread comments, it still doesn't work with my OAuth token but the call to the API succeeds with GAM OAuth token.
So it seems it's related to the OAuth token itself. An issue I get while creating the OAuth token is that I can't specify the "sub" property (or User for ServiceAccountCredentials). If I add it, I get a 403 Forbidden response with "Requested client not authorized." as error_description while generating the token i.e. before calling the API. So maybe it is the issue but I don't see how to fix it as I use an Admin email.
Another possibility is that this API needs the OAuth Client credentials as GAM requires 2 different types of credentials, Service Account and OAuth Client. As I only can use Service Account credentials in my project, I'm afraid I will be stuck if it is the case...
I don't see other options and I'm stuck with both, so any help appreciated. Thanks!
My code:
public static string GetEnterpriseUsersCount()
{
string domain = MYDOMAIN;
string certPath = System.Reflection.Assembly.GetExecutingAssembly().Location;
certPath = certPath.Substring(0, certPath.LastIndexOf("\\") + 1) + "GAMCreds.p12";
var certData = File.ReadAllBytes(certPath);
X509Certificate2 privateCertificate = new X509Certificate2(certData, "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(SERVICE_ACCOUNT_EMAIL)
{
Scopes = new[] { "https://apps-apis.google.com/a/feeds/domain/" },
User = ADMIN_EMAIL
}.FromCertificate(privateCertificate));
Task<bool> oAuthRequest = credential.RequestAccessTokenAsync(new CancellationToken());
oAuthRequest.Wait();
string uri = string.Format("https://apps-apis.google.com/a/feeds/domain/2.0/{0}/general/maximumNumberOfUsers", domain);
HttpWebRequest request = WebRequest.Create(uri) as HttpWebRequest;
if (request != null)
{
request.Method = "GET";
request.Headers.Add("Authorization", string.Format("Bearer {0}", credential.Token.AccessToken));
// Return the response
using (WebResponse response = request.GetResponse())
{
using (StreamReader sr = new StreamReader(response.GetResponseStream()))
{
return sr.ReadToEnd();
}
}
}
return null;
}
Edit: I focused on scopes like advised by Jay Lee below and it appears that the missing scope was 'https://www.googleapis.com/auth/admin.directory.domain'. However, nowhere is this written in Admin Settings API documentation page. At least, I didn't find it. 'https://apps-apis.google.com/a/feeds/domain/' is necessary too but I already added it to the list of allowed scopes. Thanks Jay!
Edit 2: I also updated the source code so that it can help in the future.
You need to grant your service account's client ID access to the scopes for admins settings API. Follow the Drive domain wide delegation instructions except sub in the correct correct scope. Then you can set sub= without an error.

WIF: How to specify which claims to return upon authentication?

Similar to this question, except I need to get additional claims at the moment of authentication, not later.
I'm implementing my RP as an authentication plugin to another website, so I can't (or rather would like to avoid if at all possible) do such things as alter the web.config file or add FederationMetadata.xml to my RP.
I've managed to sucessfully authenticate against the STS, now I need to get some more claims besides the very basics that it sends me. The STS is public (or near it), out of my control, and there will be many different RP's authenticating against it, so I don't expect that every RP will receive special treatment. I thus assume that the STS doesn't know anything about my RP until I perform an authentication, and that it will forget all about it after the authentication is complete.
As stated, I'm using WIF and I'm doing everything in code. Where do I specify which claims to send? Here's my code so far:
// Init configuration
var config = new ServiceConfiguration();
config.AudienceRestriction.AllowedAudienceUris.Add(new Uri("https://MyAudienceURI/"));
config.CertificateValidator = System.IdentityModel.Selectors.X509CertificateValidator.None;
var issuers = new ConfigurationBasedIssuerNameRegistry();
issuers.AddTrustedIssuer("08F81147C44D95CDA617963AFF0650EF26578E4A", "http://STSIssuer/trust");
config.IssuerNameRegistry = issuers;
// Create the FAM
var fam = new WSFederationAuthenticationModule();
fam.ServiceConfiguration = config;
fam.PassiveRedirectEnabled = true;
fam.Issuer = "https://STSUrl/Default.aspx";
fam.Realm = "https://MyAudienceURI/";
fam.Reply = Request.Url.ToString();
fam.RequireHttps = false;
// Check the current request
var req = System.Web.HttpContext.Current.Request;
if (!fam.CanReadSignInResponse(req, true))
{
fam.RedirectToIdentityProvider("Nop.Plugin.ExternalAuth.WIF", Request.Url.ToString(), false);
Response.End();
}
var principal = ClaimsPrincipal.CreateFromIdentities(config.SecurityTokenHandlers.ValidateToken(fam.GetSecurityToken(fam.GetSignInResponseMessage(req))));
Normally, the STS is configured to provide the claims. The configuration is on a per RP basis so different RP can get different claims.
You can augment this using WIF.
Use the ClaimsAuthenticationManager and override Authenticate.
and then something like:
((IClaimsIdentity)incomingPrincipal.Identity).Claims.Add(new Claim(...))
If the claims are external, you have to get them yourself e.g. accessing AD?

Categories

Resources