In order to prepare my application to use ADFS I have to work with federation
now we have a solution with a server with federated services using WIF for security, whe have a client consuming this services and we have and STS wich taken a usename password for identifying the user.
Everything work fine, all my claims are generated correctly and I can use them in my applcation.
Now we must use ADFS in addition of our Internal IdentityProvider, I'd just take my sts and divide it between two parts, a "federation provider" called by client and trusted by server and a part in charge of authentication
For this I just add following code in my CustomSecurityTokenHandler in FederationProvider
UserNameSecurityToken userNameTokenFromRP = token as UserNameSecurityToken;
WSTrustChannelFactory stsClient = new WSTrustChannelFactory("IdentityConfiguration");
stsClient.Credentials.UserName.UserName = userNameTokenFromRP.UserName;
stsClient.Credentials.UserName.Password = userNameTokenFromRP.Password;
IWSTrustChannelContract stsProxy = stsClient.CreateChannel();
RequestSecurityToken rst = new RequestSecurityToken(WSTrust13Constants.RequestTypes.Issue, WSTrust13Constants.KeyTypes.Symmetric);
rst.AppliesTo = new System.ServiceModel.EndpointAddress("http://localhost:8010/FederationProvider.svc");
rst.Claims.Add(new RequestClaim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", false));
rst.Issuer = new System.ServiceModel.EndpointAddress("http://localhost:8020/IdentityProvider.svc");
rst.Lifetime = new Lifetime(DateTime.Now, DateTime.Now + new TimeSpan(0, 30, 0));
rst.TokenType = Microsoft.IdentityModel.Tokens.SecurityTokenTypes.OasisWssSaml11TokenProfile11;
RequestSecurityTokenResponse rstr;
var stsToken = stsProxy.Issue(rst, out rstr);
and this in my Web.config file:
<client>
<endpoint name="IdentityConfiguration" address="http://localhost:8020/IdentityProvider.svc"
binding="ws2007HttpBinding" bindingConfiguration="SecurityTokenBinding"
contract="Microsoft.IdentityModel.Protocols.WSTrust.IWSTrustChannelContract">
<identity>
<certificate encodedValue="MyEncodedValue" />
</identity>
</endpoint>
</client>
On identity side I continue generate my claims the same way I did before
The problem I have is in my RSTR the token is null and tokenXML is encrypted, I don't understand how to use federation in this case?
If someone can help me?
thanks for reading me
Ange
Finally, I've understand what is missing
I have to create a security token handler and a Token resolver
GenericXmlSecurityToken augmentedToken = (GenericXmlSecurityToken) stsToken;
var tokenReader = new StringReader(augmentedToken.TokenXml.OuterXml);
var reader = XmlReader.Create(tokenReader);
SecurityTokenHandlerCollection handlers = SecurityTokenHandlerCollection.CreateDefaultSecurityTokenHandlerCollection();
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certificates = store.Certificates;
X509Certificate2 certificate = certificates.Find(X509FindType.FindByThumbprint, "MyThumbprint", true)[0];
List<SecurityToken> serviceTokens = new List<SecurityToken>();
serviceTokens.Add(new X509SecurityToken(certificate));
SecurityTokenResolver serviceResolver = SecurityTokenResolver.CreateDefaultSecurityTokenResolver(serviceTokens.AsReadOnly(), false);
handlers.Configuration.ServiceTokenResolver = serviceResolver;
handlers.Configuration.AudienceRestriction.AllowedAudienceUris.
Add(new Uri("http://localhost:8010/FederationProvider.svc"));
var registry = new ConfigurationBasedIssuerNameRegistry();
registry.AddTrustedIssuer("Thumbprint", "http://localhost:8020/IdentityProvider.svc");
handlers.Configuration.IssuerNameRegistry = registry;
var samlToken = handlers.ReadToken(reader);
IClaimsIdentity identity = handlers.ValidateToken(samlToken)[0];
it works fine and code's coming from alexthissen
Related
I'm using this code:
var cca = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithClientSecret(clientSecret)
.WithTenantId(tenantId)
.Build();
var ewsScopes = new [] { "https://outlook.office365.com/.default" };
var authResult = await cca.AcquireTokenForClient(ewsScopes).ExecuteAsync(cancellationToken);
var service = new ExchangeService
{
Credentials = new OAuthCredentials(authResult.AccessToken),
Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx"),
ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, "mailbox#user.com"),
TraceListener = new TraceListener(),
TraceEnabled = true,
TraceFlags = TraceFlags.All
};
Folder inbox = Folder.Bind(service, WellKnownFolderName.Inbox);
The code throws a ServiceRequestException (403) on the last line, and trace logs contains the error:
x-ms-diagnostics: 2000008;reason="The token contains not enough scope to make this call.";error_category="invalid_grant"
Do I need to expand the ewsScopes? Or is this because I'm lacking the correct permissions in Azure? Which roles/permissions do I need?
Check the token your using in
Credentials = new OAuthCredentials(authResult.AccessToken),
in jwt.io
What you should see in the roles is
If you don't have that role it means your application registration isn't correct (eg you have added the delegate permission instead of Application permission which is a common mistake).
Sorry for the multiple post about the same issue!
I'm trying to upload a self signed sertificate to application manifest created on Microsoft Registration Portal but I have some issues which I don't completly understand why, According to this answer, it's very much possible to upload the certificate using DELEGATED PERMISSIONS however I don't see the reason why I can't use Application Permissions since I only need the AccessToken and I get that with the client_credential grant flow,
Below is the code that I have tried but when retrieving the token with client_credential grant flow, I get stuck att var application = activeDirectoryClient.Applications["ApplicationObjectId"].ExecuteAsync().Result;
and when trying to use the code given to my by Tom Sung in the previous post, the applications exits with error "must have client_credentil or client_assertion in request body"
this is the code that I have tried:
private static async Task<string> GetAppTokenAsync(string graphResourceId, string tenantId, string clientId, string userId)
{
string aadInstance = "https://login.microsoftonline.com/" + tenantId + "/oauth2/token";
var clientCredential = new ClientCredential(clientId, clientSecret);
AuthenticationContext authenticationContextt =
new AuthenticationContext($"https://login.microsoftonline.com/{tenantId}/oauth2/token");
AuthenticationResult result =
await authenticationContextt.AcquireTokenAsync(graphResourceId,
clientCredential);
//token is acquiered and gets stuck
var e = result.AccessToken;
//Tom Suns code
IPlatformParameters parameters = new PlatformParameters(PromptBehavior.SelectAccount);
AuthenticationContext authenticationContext = new AuthenticationContext(aadInstance);
var authenticationResult = await authenticationContext.AcquireTokenAsync(graphResourceId, clientId, new Uri("http://localhost"), parameters, new UserIdentifier(userId, UserIdentifierType.UniqueId));
//exits with error
return authenticationResult.AccessToken;
}
try
{
var graphResourceId = "https://graph.windows.net";
var userId = "****";
//used to test if token is acquired
//var tokennn = await GetAppTokenAsync(graphResourceId, tenantID, ClientId, userId);
var servicePointUri = new Uri(graphResourceId);
var serviceRoot = new Uri(servicePointUri, tenant);
var activeDirectoryClient = new ActiveDirectoryClient(serviceRoot, async () => await GetAppTokenAsync(graphResourceId, tenantID, ClientId, userId));
AsymmetricKeyParameter myCAprivateKey = null;
//generate a root CA cert and obtain the privateKey
X509Certificate2 MyRootCAcert = CreateCertificateAuthorityCertificate("CN=OutlookIntegration", out myCAprivateKey);
//add CA cert to store
addCertToStore(MyRootCAcert, StoreName.Root, StoreLocation.LocalMachine);
var expirationDate = DateTime.Parse(MyRootCAcert.GetExpirationDateString()).ToUniversalTime();
var startDate = DateTime.Parse(MyRootCAcert.GetEffectiveDateString()).ToUniversalTime();
var binCert = MyRootCAcert.GetRawCertData();
var keyCredential = new KeyCredential
{
CustomKeyIdentifier = MyRootCAcert.GetCertHash(),
EndDate = expirationDate,
KeyId = Guid.NewGuid(),
StartDate = startDate,
Type = "AsymmetricX509Cert",
Usage = "Verify",
Value = binCert
};
//gets stuck here when using clientsecret grant type
var application = activeDirectoryClient.Applications["ApplicationObjectId"].ExecuteAsync().Result;
application.KeyCredentials.Add(keyCredential);
application.UpdateAsync().Wait();
}
catch (Exception exception)
{
Console.WriteLine(exception);
throw;
}
I am now completly stuck, Anyone have any idea why it doesn't work with Application Permissions or why it gets stuck at var application = activeDirectoryClient.Applications["ApplicationObjectId"].ExecuteAsync().Result;
Edit 1
is it because I have my app as a web app/API that uses username and password to authenticate?
Based on my test if we want to change the keyCredential, DELEGATED PERMISSIONS is required.
If we want to update Azure AD application other properties, we could use Application Permissions.
Reference:
Azure Active Directory developer glossary
"Delegated" permissions, which specify scope-based access using delegated authorization from the signed-in resource owner, are presented to the resource at run-time as "scp" claims in the client's access token.
"Application" permissions, which specify role-based access using the client application's credentials/identity, are presented to the resource at run-time as "roles" claims in the client's access token.
I have an mvc 5 application with an web api 2. (.NET 4.6)
I implemented the oauth2 configuration next to the authentication for my mvc app (app.UseCookieAuthentication) :
OAuthAuthorizationServerOptions OAuthServerOptions = new
OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),
Provider = new AspNetIdentityOAuthAuthorizationServerProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(1000),
};
app.UseOAuthBearerTokens(OAuthServerOptions);
My apis are protected with the Authorize Attribute (global filter).
I use the client credentials grant
I followed these two articles (that are the same)
https://learn.microsoft.com/en-us/aspnet/web-api/overview/security/individual-accounts-in-web-api
https://mitchelsellers.com/blogs/2017/05/10/adding-webapi-oauth-authentication-to-an-existing-project
I'm able to get a token for my user, but when I want to use the token to access my Api, I get a 403 forbidden error
HttpClient client = new HttpClient();
Dictionary<string, string> parameters = new Dictionary<string, string>();
parameters.Add("grant_type", "client_credentials");
parameters.Add("client_id", "4rclFahG7gho8erzbsmTbw==");
parameters.Add("client_secret", "IBSqiYb0kT/lzV0gpQsPxkUDI9ztu0dhHWDe4VQDzKGYm2pl+75sMVfEsoGo4FAxFm0qZUFcDrVMrfqYhn2bzw==");
var content = new FormUrlEncodedContent(parameters);
try
{
HttpResponseMessage result = client.PostAsync("http://localhost:49594/oauth/token", content).Result;
string jsonResult = result.Content.ReadAsStringAsync().Result;
var resultObject = JsonConvert.DeserializeObject<TokenResult>(jsonResult);
var accessToken = resultObject.access_token;
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + accessToken);
result = client.GetAsync("http://localhost:49594/api/v1/echo?id=myt
estvalue").Result;
// RESULT is 403 - Forbidden
I tested with postman as well, and the result is the same.
did anyone experienced the same problem ?
do you know what I' missing ?
Update :
It's working if I deploy my application on a server (azure app services) but still not on my machine
I found the reason of my issue !
I'm using stuntman (https://rimdev.io/stuntman/) for my dev and I forgot to configure it for oauth ...
This line was missing :
StuntmanOptions.AllowBearerTokenPassthrough = true;
For user authentication with external providers such as Google, it is using specific Owin middlewares. As for example Microsoft.Owin.Security.Google. WebAPI2 template uses this to support implicit flow authentication (response_type=token). But what about Code flow?
Is it possible to implement Code flow (response_type=code)?
After debugging those OAuth providers I noticed that passing return_type=code to Google, it successfully authenticates and returns json with access and refresh tokens, then user gets signed in by api/Account/ExternalLogin endpoint but at the end of the flow I get redirected to
http://localhost:50321/?error=unsupported_response_type#.
I could not really find the flow where and why it is setting this specific error in the assembly.
Startup.Auth.cs looks like this:
public void ConfigureAuth(IAppBuilder app)
{
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
PublicClientId = "self";
var tokenTimeSpanInHours = ConfigurationManager.AppSettings["AccessTokenLifeTimeInHours"];
OAuthServerOptions = new OAuthAuthorizationServerOptions
{
Provider = new ApplicationOAuthProvider(PublicClientId),
TokenEndpointPath = new PathString("/api/token"),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(Convert.ToInt16(tokenTimeSpanInHours)),
AllowInsecureHttp = true
};
app.UseOAuthBearerTokens(OAuthServerOptions);
var googleOAuthOptions = new GoogleOAuth2AuthenticationOptions
{
AccessType = "offline",
Provider = new CustomGoogleAuthProvider(),
ClientId = ConfigurationManager.AppSettings["GoogleAccountClientId"].ToString(),
ClientSecret = ConfigurationManager.AppSettings["GoogleAccountClientSecret"].ToString()
};
googleOAuthOptions.Scope.Add("profile");
googleOAuthOptions.Scope.Add("email");
googleOAuthOptions.Scope.Add("https://www.googleapis.com/auth/gmail.send");
app.UseGoogleAuthentication(googleOAuthOptions);
}
Where is the problem then? Do I need some explicit configuration to tell that I want code flow? Is it supported?
I have an ASP.NET app that uses Azure ACS (and indirectly ADFS) for Authentication - which all works fine. Now I've been asked to pass the SessionToken to another backend service where it can be verified and the claims extracted. [Long Story and not my choice]
I'm having fits on the decryption side, and I'm sure I'm missing something basic.
To set the stage, the error upon decryption is:
ID1006: The format of the data is incorrect. The encryption key length is negative: '-724221793'. The cookie may have been truncated.
The ASP.NET website uses the RSA wrapper ala:
void WSFederationAuthenticationModule_OnServiceConfigurationCreated(object sender, ServiceConfigurationCreatedEventArgs e)
{
string thumbprint = "BDE74A3EB573297C7EE79EB980B0727D73987B0D";
X509Certificate2 certificate = GetCertificate(thumbprint);
List<CookieTransform> sessionTransforms = new List<CookieTransform>(new CookieTransform[]
{
new DeflateCookieTransform(),
new RsaEncryptionCookieTransform(certificate),
new RsaSignatureCookieTransform(certificate)
});
SessionSecurityTokenHandler sessionHandler = new SessionSecurityTokenHandler(sessionTransforms.AsReadOnly());
e.ServiceConfiguration.SecurityTokenHandlers.AddOrReplace(sessionHandler);
}
(the thumbprint is the same value as added by FedUtil in web.config.
I write the token with:
if (Microsoft.IdentityModel.Web.FederatedAuthentication.SessionAuthenticationModule.TryReadSessionTokenFromCookie(out token))
{
Microsoft.IdentityModel.Tokens.SessionSecurityTokenHandler th = new Microsoft.IdentityModel.Tokens.SessionSecurityTokenHandler();
byte[] results = th.WriteToken(token);
...
which gives me:
<?xml version="1.0" encoding="utf-8"?>
<SecurityContextToken p1:Id="_53382b9e-8c4b-490e-bfd5-de2e8c0f25fe-94C8D2D9079647B013081356972DE275"
xmlns:p1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns="http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512">
<Identifier>urn:uuid:54bd1bd7-1110-462b-847e-7f49c1043b32</Identifier>
<Instance>urn:uuid:0462b7d7-717e-4ce2-b942-b0d6a968355b</Instance>
<Cookie xmlns="http://schemas.microsoft.com/ws/2006/05/security">AQAAANCMnd blah blah 1048 bytes total
</Cookie>
</SecurityContextToken>
and, with the same Certificate on the other box (and the token read in as a file just for testing), I have:
public static void Attempt2(FileStream fileIn, X509Certificate2 certificate, out SecurityToken theToken)
{
List<CookieTransform> sessionTransforms = new List<CookieTransform>(new CookieTransform[]
{
new DeflateCookieTransform(),
new RsaSignatureCookieTransform(certificate),
new RsaEncryptionCookieTransform(certificate)
});
SessionSecurityTokenHandler sessionHandler = new SessionSecurityTokenHandler(sessionTransforms.AsReadOnly());
// setup
SecurityTokenResolver resolver;
{
var token = new X509SecurityToken(certificate);
var tokens = new List<SecurityToken>() { token };
resolver = SecurityTokenResolver.CreateDefaultSecurityTokenResolver(tokens.AsReadOnly(), false);
}
sessionHandler.Configuration = new SecurityTokenHandlerConfiguration();
sessionHandler.Configuration.IssuerTokenResolver = resolver;
using (var reader = XmlReader.Create(fileIn))
{
theToken = sessionHandler.ReadToken(reader);
}
}
and then ReadToken throws a FormatException of
ID1006: The format of the data is incorrect. The encryption key length is negative: '-724221793'. The cookie may have been truncated.
At this point, I can't tell if my overall approach is flawed or if I'm just missing the proverbial "one-line" that fixes all of this.
Oh, and I'm using VS2010 SP1 for the website (.NET 4.0) and I've tried both VS2010SP1 .NET 4.0 and VS2012 .NET 4.5 on the decoding side.
Thanks!
Does your app pool account for the backend service have read access to the certificate? If not give your app pool account for the backend service read access to the certificate. I had problems in the past with encryption/decryption because of this.
This might help, this will turn your FedAuth cookies into a readable XML string like:
<?xml version="1.0" encoding="utf-8"?>
<SecurityContextToken p1:Id="_548a372e-1111-4df8-b610-1f9f618a5687-953155F0C35B4862A5BCE4D5D0C5ADF0" xmlns:p1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns="http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512">
<Identifier>urn:uuid:c9f9b733-1111-4b01-8af3-23c8af3e19a6</Identifier>
<Instance>urn:uuid:ee955207-1111-4498-afa3-4b184e97d0be</Instance>
<Cookie xmlns="http://schemas.microsoft.com/ws/2006/05/security">long_string==</Cookie>
</SecurityContextToken>
Code:
private string FedAuthToXmlString(string fedAuthCombinedString)
{
// fedAuthCombinedString is from FedAuth + FedAuth1 cookies: just combine the strings
byte[] authBytes = Convert.FromBase64String(fedAuthCombinedString);
string decodedString = Encoding.UTF8.GetString(authBytes);
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
var thumbprint = "CERT_THUMBPRINT"; // from config
var cert = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false)[0];
var sessionTransforms = new List<System.IdentityModel.CookieTransform>(new System.IdentityModel.CookieTransform[]
{
new System.IdentityModel.DeflateCookieTransform(),
new System.IdentityModel.RsaSignatureCookieTransform(cert),
new System.IdentityModel.RsaEncryptionCookieTransform(cert)
});
SessionSecurityTokenHandler sessionHandler = new SessionSecurityTokenHandler(sessionTransforms.AsReadOnly());
SecurityTokenResolver resolver;
{
var token = new X509SecurityToken(cert);
var tokens = new List<SecurityToken>() { token };
resolver = SecurityTokenResolver.CreateDefaultSecurityTokenResolver(tokens.AsReadOnly(), false);
}
sessionHandler.Configuration = new SecurityTokenHandlerConfiguration();
sessionHandler.Configuration.IssuerTokenResolver = resolver;
var i = 0; // clear out invalid leading xml
while ((int)decodedString[i] != 60 && i < decodedString.Length - 1) i++; // while the first character is not <
store.Close();
return decodedString.Substring(i);
}