Kerberos Token asks to be called again to complete the context - c#

I am attempting to obtain a Kerberos Token from a C# Application (not web-based, a standalone executable).
I have referred to this question to figure out how to do it, but even trying both answers, I get the same problem.
When I reach the GetToken line (using Furkat's answer as a reference here), I get an exception:
KerberosRequestorSecurityToken securityToken = tokenProvider.GetToken(TimeSpan.FromMinutes(1)) as KerberosRequestorSecurityToken;
Here is the exception and the nested innerExceptions:
Exception: The NetworkCredentials provided were unable to create a Kerberos credential, see inner exception for details.
innerException: Authenticating to a service running under a user account which requires Kerberos multilegs, is not supported.
innerException: The function completed successfully, but must be called again to complete the context
I have some serious problems trying to find any examples of this working for a non-web based application, the StackOverflow question I linked is pretty much the closest I've got to getting what I need.
I also have problems figuring out exactly how things are supposed to work, since I can't get an example to work on my side. I'm looking for some sort of unique token for the user, that can then be passed to a SAML POST call to a server for Single Sign On. What will this token look like? Is it right to use TokenImpersonationLevel.Impersonation, instead of Identification here? (Identification gives me the same problem).
So my question is about my error and how to fix it, but I would really appreciate an explanation with the answer, telling me about the context (what was going wrong, what I misunderstood, etc).
Here's my complete Method. It's in Proof-Of-Concept stage right now, so forgive the temporary bad naming and ugly code. I'm making lots of trial-and-error.
public string Method5()
{
try
{
var userName1 = new WindowsPrincipal(WindowsIdentity.GetCurrent()).Identity.Name;
var domainName = userName1.Split('\\').First();
var userName = userName1.Split('\\').Last();
AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
var domain = Domain.GetCurrentDomain().ToString();
using (var domainContext = new PrincipalContext(ContextType.Domain, domain))
{
string spn = UserPrincipal.FindByIdentity(domainContext, IdentityType.SamAccountName, userName).UserPrincipalName;
KerberosSecurityTokenProvider tokenProvider = new KerberosSecurityTokenProvider(spn, TokenImpersonationLevel.Impersonation, CredentialCache.DefaultNetworkCredentials);
KerberosRequestorSecurityToken securityToken = tokenProvider.GetToken(TimeSpan.FromMinutes(1)) as KerberosRequestorSecurityToken;
string serviceToken = Convert.ToBase64String(securityToken.GetRequest());
return serviceToken;
}
}
catch (Exception ex)
{
return "Failure";
}
}

The error indicates that you are requesting a Kerberos User2User token. The multileg bit is correct, but somewhat misleading. The issue is that AD determines it's a U2U request and makes the API return a specific error, indicating it's U2U and requires a retry with different parameters. .NET doesn't understand this retry, hence the error.
The reason you're requesting a U2U token is because you're calling the token provider asking for it to request a token to access the given SPN, which in this case is just an ordinary user. This is generally not useful in client/server applications.
KerberosSecurityTokenProvider tokenProvider = new KerberosSecurityTokenProvider(spn, TokenImpersonationLevel.Impersonation, CredentialCache.DefaultNetworkCredentials);
What this code is doing is saying for a user that has been inferred by impersonation or authentication previously, request a token so that user can access a remote service {SPN}. A token is only useful for a single user to a single service. You can't just collect a token and user it everywhere. This is not how Kerberos-proper works. Kerberos determines the name of that service by the SPN. In this case it already knows who the caller is.
So, the correct solution is:
var identity = Thread.CurrentPrincipal.Identity; // domain\username1
var spn = "host/someservice.domain.com";
var tokenProvider = new KerberosSecurityTokenProvider(spn);
var securityToken = tokenProvider.GetToken(TimeSpan.FromMinutes(1)); // token for username1 to host/someservice.domain.com

Related

How do I generate an assertion token for Sharepoint Access on behalf of another user in C#

I have a web application with a number of modules. One of the modules grabs a number of excel files from SharePoint directories, and then combines the data in them. So far, I have been just mapping the folders to OneDrive and accessing them that way. But this always uses my OneDrive credentials, which need to be refreshed from time to time. The right way to do this is to access them directly from Sharepoint on behalf of the user logged into my web application. I have the delegated API permission things set up in Azure, and I have the client ID and secret, etc.. I've been reading a number of articles on how to do this. All of them talk about how to get the token on behalf of someone else. These articles also talk about the assertion token needing to be passed in order to get the on behalf of token. However, they don't tell you how to get the assertion token in the first place. Here is the code I currently have:
'''var client = new RestClient("https://login.microsoftonline.com/XXXX/oauth2/v2.0/token");
var request = new RestRequest();
request.Method = Method.Post;
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
request.AddParameter("client_id", "MYCLIENTID", ParameterType.GetOrPost);
request.AddParameter("client_secret", "MYSECRET", ParameterType.GetOrPost);
request.AddParameter("scope", "https://MYTenent.sharepoint.com/.default", ParameterType.GetOrPost);
request.AddParameter("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer", ParameterType.GetOrPost);
request.AddParameter("requested_token_use", "on_behalf_of", ParameterType.GetOrPost);
RestResponse response = client.Execute(request);'''
The result of this is of course an error that the assertion was not supplied. I didn't supply any more code, because I can't even get passed this. The rest of my code takes the token and passes it to an auth provider, which is then used to instantiate the GraphServiceClient. Based on what I've read, that client is then used to get the lists, files, etc...
So, my question is, how do I get the assertion token in the first place? I'm hoping the code I have written so far is in the correct direction and all I'm missing is the assertion token.
UPDATE:
I've gotten one answer that really didn't help. I pretty much copied and pasted the code (replacing the clientID, etc..) and I received an error> I was going to copy and paste it from the solution comments provided in the answer, but I guess you can't do that while editing.
Someone also asked if I was able to get the auth code from the URL. The answer to that is no. We use 2 factor authentication, and I tried to manually look at the URLS as I was logging in, while using break points to slow things down a bit. And I did not see an auth code. I did put a break point directly after the line of code:
var info = await _signInManager.GetExternalLoginInfoAsync();
And when I look at the info variable, I can see 4 tokens. One of them is an access token and another is an ID token. The last one is an expiration date. I don't see an auth code, and from what I understand, by the time I see the access code, it's too late. The auth code was already used to get the access code.
UPDATE 2:
I know that OBO is not what I want. I also know that in order to use delegated permissions, I need to use the Auth Code flow and not client credentials. I can't seem to get the auth code from the users initial log in. And I don't know how to get it otherwise.
For those of you that might be thinking "Does he need to be spoon fed?", the answer is yes, I do. I need a simple code example that will get me the auth code, so I can use it in the rest of the code I already have. If anyone can paste that code into an answer and not provide a link, that would be great. I'm sorry, but the links I have been given, just go to microsoft learn sites that go through the explaination, but don't give any code samples.
The OBO flow is obviously not applicable in your context, and if you're going to get an access token on behalf of a logged in user, then you should focus on auth code flow or ROPC flow.
The corresponding C# code segment is:
using Microsoft.Graph;
using Azure.Identity;
var scopes = new[] { "https://{tenant-name}.sharepoint.com/.default" };
// Multi-tenant apps can use "common",
// single-tenant apps must use the tenant ID from the Azure portal
var tenantId = "tenant id";
// Values from app registration
var clientId = "client id";
var clientSecret = "client secret";
// For authorization code flow, the user signs into the Microsoft
// identity platform, and the browser is redirected back to your app
// with an authorization code in the query parameters
var authorizationCode = "authorization code ";
// using Azure.Identity;
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
// https://learn.microsoft.com/dotnet/api/azure.identity.authorizationcodecredential
var authCodeCredential = new AuthorizationCodeCredential(
tenantId, clientId, clientSecret, authorizationCode, options);
var accessToken = await authCodeCredential.GetTokenAsync(new Azure.Core.TokenRequestContext(scopes) { });
Console.WriteLine(accessToken.Token);
//var graphClient = new GraphServiceClient(authCodeCredential, scopes);

Differences between AcquireTokenAsync and LoginAsync in Xamarin Native

TL;DR
What is the difference between authenticating users with AuthenticationContext.AcquireTokenAsync() and MobileServiceClient.LoginAsync() ?
Can I use the token from the first method to authenticate a user in the second?
Long Version
I've been trying to authenticate users via a mobile device (iOS) for a mobile service in Azure with Xamarin Native (not Forms).
There are enough tutorials online to get you started but in the process, I got lost and confused...
What's working at the moment is the following; which has the user enter his credentials in another page and returns a JWT token which (if decoded here1) has the claims listed here2.
Moreover, this token is authorized in controllers with the [Authorize] attribute in requests with an Authorization header and a Bearer token.
Note: the following constants are taken from the registered applications in Active Directory (Native and Web App / API).
public const string Authority = #"https://login.windows.net/******.com";
public const string GraphResource = #"https://*******.azurewebsites.net/********";
public const string ClientId = "046b****-****-****-****-********0290";
public const string Resource = #"https://******.azurewebsites.net/.auth/login/done";
var authContext = new AuthenticationContext(Authority);
if (authContext.TokenCache.ReadItems().Any(c => c.Authority == Authority))
{
authContext = new AuthenticationContext(authContext.TokenCache.ReadItems().First().Authority);
}
var uri = new Uri(Resource);
var platformParams = new PlatformParameters(UIApplication.SharedApplication.KeyWindow.RootViewController);
AuthenticationResult authResult = await authContext.AcquireTokenAsync(GraphResource, ClientId, uri, platformParams);
Another working authentication flow I tried is the following; which does the same with the difference that it informs the user that the app requires permissions to access some resources.
If allowed, a JWT token (with less characters than the previous one) is returned with less payload data. This token though, won't pass the authorization attribute just like the previous one.
public const string AadResource = #"https://******.azurewebsites.net/.auth/aad";
var client = new MobileServiceClient(AadResource);
var rootView = UIApplication.SharedApplication.KeyWindow.RootViewController;
MobileServiceUser user = await client.LoginAsync(rootView, "aad");
Obviously, the return type is different, but, what is the main difference between these two authentication methods?
Additionally, another headache comes from trying to achieve this3 at the very end of the article. I already have the token from the first aforementioned method but when I try to follow the client flow with the token in client.LoginAsync() the following error is returned:
The resource you are looking for has been removed, had its name changed, or is temporarily unavailable.
Link References:
https://jwt.io/
https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-token-and-claims
https://adrianhall.github.io/develop-mobile-apps-with-csharp-and-azure/chapter2/enterprise/
https://www.reddit.com/r/xamarindevelopers/comments/6dw928/differences_between_acquiretokenasync/
Edit (30 May 2017)
The Why are they different? has been answered on this4 reddit post by the same person (pdx mobilist / saltydogdev) and the simple answer is claims.
Yes. You can insert a token into the MobileServicesClient and then use it had been authenticated directly. That's the beauty of bearer tokens.
Just set the MobileServiceClient CurrentUser:
MobileServiceclient Client;
...
Client.CurrentUser = new MobileServiceUser(username)
{ MobileServiceAuthenticationToken = authtoken};
Edit:
The reason they are different is because each library is requesting a different set of claims. The reason they still work is that the basic information for authenticating/validating the token is there. I'm not sure what are the specific required claims. At a minimum it would be the user id AND that the signature is valid. They are doing the same basic thing, MobileServiceClient just requests less claims.
I believe that the MobileServicesClient can authenticate against Azure AD, if you set up the mobile service correctly. So you should be able to just use the MobileServiceClient.
Here is the document that describes how this works: https://learn.microsoft.com/en-us/azure/app-service-mobile/app-service-mobile-how-to-configure-active-directory-authentication

Impersonation, Active Directory, and "user does not have authority to xxxx" issues

I have 2 ASP.NET MVC 3 applications. I am using impersonation via the web.config to allow me to query Active Directory to get details on the user. The application uses Windows authentication and does not allow anonymous users. One application is the primary application where the user performs their tasks. The other allows the user to set up other user's to look like them in application one.
The test user's are getting the following error:
SQL1092N "<DOMAIN ID>" does not have the authority to perform the requested command.
This happens after I send a web request from my primary application to the secondary one. To get that working I had to make the request impersonate the actual user and not the identity the application uses for impersonation. This is actually an SO question I posted and had answered. That's here: How do I call an MVC Action via a WebRequest and validate the request through Active Directory?
At the end of that code, I call:
impersonationContext.Undo();
It is after this web request takes place, that the primary application tries accessing the database and now it seems that the above call has undone the impersonation of the application, so the user's attempt to do anything that opens a database connection fails. At least, that's my working theory after a day of head bashing.
My question is, how can I get the impersonation of the application to revert back to the user in the web.config? Or, when making my web request, is there a way to ensure the impersonation context only applies to that request?
The whole point of all of this is that the second application has its own sql server database. The primary application uses DB2. I would like to write the database access code once, but use it in both applications. Currently that's what I've done, but my method of relying on the web request to get the data may not be the best approach.
I'm open to any thoughts, comments, suggestions, and/or criticism. How should I go about handling this?
Okay...my theory that the IPrincipal context was changed when making the web request proved accurate, which made this fix extremely easy. Best part is, I can keep using the api I built to make this request without duplicating the Sql Server Entity Framework parts.
I have the following call to my api library:
proxyRequestResultDetails = ProxyApiWrapper.GetProxies(
adUserInfo.AssociateId,
context.User);
This code is being called by an authorization filter attribute. The method prototype looks like
public void OnAuthorization(AuthorizationContext filterContext)
Internally, the call makes the GetProxies method following call:
public static StreamReader GetWebRequestStream(
string url,
string contentType,
bool useDefaultCredentials,
IPrincipal user)
{
var impersonationContext = ((WindowsIdentity)user.Identity).Impersonate();
var request = WebRequest.Create(url);
try
{
request.ContentType = contentType;
//request.ImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
//request.UseDefaultCredentials = useDefaultCredentials;
//IWebProxy p = new WebProxy();
//request.Proxy = p.
request.AuthenticationLevel = System.Net.Security.AuthenticationLevel.MutualAuthRequested;
request.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
var response = (HttpWebResponse)request.GetResponse();
return new StreamReader(response.GetResponseStream());
}
catch (Exception e)
{
impersonationContext.Undo();
throw e;
}
finally
{
impersonationContext.Undo();
}
}
When the calling method returns, the identity of user is no longer that of the one set for the application to
impersonate. The fix is pretty simple:
//Track current identity before proxy call
IPrincipal user = context.User;
proxyRequestResultDetails = ProxyApiWrapper.GetProxies(
adUserInfo.AssociateId,
context.User);
//Undo any impersonating done in the GetProxies call
context.User = user;
2 lines of code resolved 12 hours of head ache. It could have been worse. Anyhow. Thanks for being a sounding board. I tried
having this conversion with the duck, but the duck got confused.

Is validating a Realm sufficient security?

I'm working on an OpenId Provider for an SSO setup - it's basically a web application portal that shares credentials with any of the "applications" the user has access to. I have the Provider set up and everything is working fine, but I have a question about security.
I want to do some permissions checking on the Provider before it sends a positive assertion to the RP; namely that the user actually has permissions to the application which is making the request.
Here's the Provider code I've got at the moment (just a snippet, can add more if necessary):
private bool AutoRespondIfPossible(out ActionResult response)
{
if (ProviderEndpoint.PendingRequest.IsReturnUrlDiscoverable(OpenIdProvider.Channel.WebRequestHandler) == RelyingPartyDiscoveryResult.Success
&& User.Identity.IsAuthenticated && this.RealmIsValid(ProviderEndpoint.PendingAuthenticationRequest.Realm)) {
if (ProviderEndpoint.PendingAuthenticationRequest != null) {
if (ProviderEndpoint.PendingAuthenticationRequest.IsDirectedIdentity
|| this.UserControlsIdentifier(ProviderEndpoint.PendingAuthenticationRequest)) {
ProviderEndpoint.PendingAuthenticationRequest.IsAuthenticated = true;
response = this.SendAssertion();
return true;
}
}
//we don't want anon requests
if (ProviderEndpoint.PendingAnonymousRequest != null) {
ProviderEndpoint.PendingAnonymousRequest.IsApproved = false;
response = this.SendAssertion();
return true;
}
}
response = null;
return false;
}
Basically what I'm doing is validating that the realm of the request (in the RealmIsValid method) matches to a hostname in my list of acceptable hostnames, and then I'm comparing the user permissions based on the hostname.
What I'm wondering is: How accurate is ProviderEndpoint.PendingAuthenticationRequest.Realm? If I understand correctly, the realm is set by the relying party - is it possible that the endpoint could receive a request from a URI other than the realm specified in that request? Or am I safe to assume that the realm will always be accurate (that is: match the URI of the relying party)?
Yes, the OpenID realm is reliable, due to two steps OpenID 2.0 and DotNetOpenAuth takes:
The OpenID return_to URL must be a derivative of the realm URL. So while anyone can formulate an OpenID request as if it came from any relying party, the alleged relying party will always be the one to actually receive the response, so an attacker operating another RP will not get the response.
Some "open redirector" attacks might allow the attacker to use a return_to URI that is based on a legitimate Realm URL, but happens to be a URL that will redirect to the attacker's web site, thus delivering the assertion to the attacker. This is mitigated by "RP Discovery" which your code snippet includes with its call to the IsReturnUrlDiscoverable method. The RP should explicitly list the allowed return_to URLs in its RP Discovery XRDS document, so that open redirector endpoints are not allowed.
That all said, OpenID is mostly about identifying the user -- not authorizing them to specific RPs. So while what you're doing may be fine, it's a bit off the beaten track for OpenID use, so please consider the security implications carefully (as it sounds like you're doing now).

Creating Delegation token - can't create a SecurityTokenService

I'm trying to build a system working with ADFS and claims. At the moment, this is just a "toy" implementation.
I've built a very simple MVC web application, set it up using the "Identity and Access..." wizard in Visual Studio to talk to an ADFS 2.0 server, and deployed it to an IIS server. All works fine, and I can examine and list the received claims.
The next step is to build a Web API based REST service (representing back-end services that the MVC application is going to depend on), so I want to pass the credentials across to that back-end server so that it can make suitable authorization decisions.
So the first step is for me to create the delegation token (and I'll then, hopefully, work out what to do with it in terms of the HttpClient class to make the rest call). I've got this:
//We need to take the bootstrap token and create an appropriate ActAs token
var rst = new RequestSecurityToken
{
AppliesTo = new EndpointReference("https://other-iis.example.com/Rest"),
RequestType = RequestTypes.Issue,
KeyType = KeyTypes.Symmetric,
ActAs = new SecurityTokenElement(((BootstrapContext)((ClaimsIdentity)User.Identity).BootstrapContext).SecurityToken)
};
var sts = new SecurityTokenService(); //This line isn't valid
var resp = sts.Issue(System.Threading.Thread.CurrentPrincipal as ClaimsPrincipal, rst);
But, the issue is that SecurityTokenService is abstract. I can't find any types derived from this class in either System.IdentityModel nor System.IdentityModel.Services, and the above doesn't include any reference to the ADFS server which I'll obviously need to provide at some point.
Of course, I may be going down completely the wrong route also, or am just hitting a minor stumbling block and not seeing a much larger one looming in the distance, so any advice on that would be appreciated also.
I've looked at, for example, Identity Delegation Scenario, but that uses CreateChannelActingAs, which I don't think is going to work when I'm talking to a rest service (or will it?), and also doesn't seem to apply to .NET 4.5.
I am requesting tokens from an ADFS 2.0 for caching and looking at the DisplayToken. Maybe this can help you get started.
Here is what I can up with:
public SecurityToken GetToken(out RequestSecurityTokenResponse rstr)
{
Console.WriteLine("Connecting to STS...");
WSTrustChannelFactory factory = null;
try
{
if (_useCredentials)
{
// use a UserName Trust Binding for username authentication
factory =
new WSTrustChannelFactory(
new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
"https://<adfs>/adfs/services/trust/13/UsernameMixed");
factory.TrustVersion = TrustVersion.WSTrust13;
// Username and Password here...
factory.Credentials.UserName.UserName = "username";
factory.Credentials.UserName.Password = "password";
}
else
{
// Windows authentication over transport security
factory = new WSTrustChannelFactory(
new WindowsWSTrustBinding(SecurityMode.Transport),
"https://<adfs>/adfs/services/trust/13/windowstransport") { TrustVersion = TrustVersion.WSTrust13 };
}
var rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
AppliesTo = SvcEndpoint,
KeyType = KeyTypes.Symmetric,
RequestDisplayToken = true
};
Console.WriteLine("Creating channel for STS...");
IWSTrustChannelContract channel = factory.CreateChannel();
Console.WriteLine("Requesting token from " + StsEndpoint.Uri);
SecurityToken token = channel.Issue(rst, out rstr);
Console.WriteLine("Received token from " + StsEndpoint.Uri);
return token;
}
finally
{
if (factory != null)
{
try
{
factory.Close();
}
catch (CommunicationObjectFaultedException)
{
factory.Abort();
}
}
}
}
You might have to acivate the UsernameMixed Endpoint in your ADFS 2.0 if you want to use it and don't forget to restart the service afterwards!
From msdn
To create an STS you must derive from the SecurityTokenService class. In your custom class you must, at a minimum, override the GetScope and GetOutputClaimsIdentity methods.
Not sure how much this will help you, but You're not supposed to create a SecurityTokenService. You are not creating a new token here, and your aplication is not supposed to act as the STS - this is what the AD FS is for.
Your application should only delegate the token received from the AD FS to the service (the concept is described in the link from msdn you provided in your question)
Im guessing theres a good chance the web api will suppor this as well, as its built upon wcf, and from the http point of view - theres no reason it wont support a ws-federation/saml 2 tokens.
EDIT:
This video (starting at 35:00+-) shows a way, i think, to implement what youre looking for, with ws-federation saml token. im guessing its also possible with a saml2 token

Categories