Can anyone point to sample code for actively issuing a RequestSecurityToken using the NT credentials of the Thread.CurrentPrincipal as ClaimsPrincipal
?
The scenario is an asp.net web app with windows authentication enabled (so there is an authenticated WindowsIdentity). My desire is to call the STS actively rather than enabling passiveRedirect, and to do this using the .Net 4.5 identity libraries.
Most code samples, such as Claims Helper for Windows Phone or Using an Active STS set the credentials with a username/pwd input and UserNameWSTrustBinding.
I thought the solution might involve impersonation or a call to channelFactory.CreateChannelWithActAsToken()
with the a token created from the windows identity.
-- The following .Net4.5 code does get a GenericXmlSecurityToken when hitting an /adfs/services/trust/13/windowsmixed endpoint. However, the claims are for the domain account under which the site is running, and not the domain account of the authenticated user. When I switch the endpoint to /adfs/services/trust/13/kerberossmixed, I get "cannot negotiate" errors as documented in several questions and forums, but I cannot apply any offered solutions with .Net 4.5. One of the classes not ported over from Microsoft.IdentityModel is the KerberosWSTrustBinding...
public static void CallSts()
{
try
{
var wsMod = FederatedAuthentication.WSFederationAuthenticationModule;
var appliesToEp = new EndpointReference(wsMod.Realm);
var stsEp = new EndpointAddress(new Uri(wsMod.Issuer), EndpointIdentity.CreateSpnIdentity("stsSpn"));
var msgBinding = new WS2007HttpBinding(SecurityMode.TransportWithMessageCredential, false);
msgBinding.Security.Message.EstablishSecurityContext = false;
msgBinding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;
using(var factory = new WSTrustChannelFactory(msgBinding, stsEp))
{
factory.Credentials.SupportInteractive = false;
factory.TrustVersion = TrustVersion.WSTrust13;
var myRst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
AppliesTo = appliesToEp,
KeyType = KeyTypes.Bearer,
};
var channel = factory.CreateChannel();
var stsToken = channel.Issue(myRst) as GenericXmlSecurityToken;
if(stsToken != null)
{
Log.DebugFormat("Reply Token is {0}", stsToken.GetType().Name);
var handlers = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.SecurityTokenHandlers;
var token = handlers.ReadToken(new XmlTextReader(new StringReader(stsToken.TokenXml.OuterXml)));
var identity = handlers.ValidateToken(token).First();
//TODO write to session
}
else
{
Log.Debug("Reply Token is null.");
}
}
}
catch(Exception ex)
{
Log.Error("Rst.Call has failed", ex);
}
}
For @leastprivilege suggestion, I add this code:
var user = Thread.CurrentPrincipal as ClaimsPrincipal;
var winId = user.Identity as WindowsIdentity;
if(winId != null)
{
// shows my domain account after I was prompted for credentials;
// my domain account does not exist on the client machine, so it is a true domain credential
Log.DebugFormat("WindowsIdentity Name is {0}", winId.Name);
}
using(winId.Impersonate())
{
// again, shows my domain account
Log.DebugFormat("Impersonation Context {0}", WindowsIdentity.GetCurrent(true).Name);
var channel = factory.CreateChannel();
var stsToken = channel.Issue(myRst) as GenericXmlSecurityToken;
// request is issued, but results in SecurityNegotiationException: The caller was not authenticated by the service.
}
Which fails with "The caller was not authenticated by the service". The same STS will authenticate my domain account in a passive redirect scenario...so although I know I am doing something wrong, the account itself should be recognized.
Update:
I received a notification that this question received a notable number of views, so I will offer the following as one workaround: Although we configured our servers for delegation (as Dominick suggested below), we still did not surmount the double-hop issue. If I remember, we hit a roadblock from simple network management policies above our local IT that any enterprise might hit as well. So, while impersonating over a double-hop against a server with Windows Authentication is not allowed, credentials can be impersonated over a double hop using Basic Authentication. This may or may not be an acceptable situation (intranet for our case). If you do, you would add
msgBinding.Security.Message.NegotiateServiceCredential = true;
to the above ChannelBinding configuration.