I have created a WCF service that has CRUD operations to Outlook using Graph API. it is working in my local but when I deployed it in our UAT server I get a 403 forbidden in this part:
result = _authContext.AcquireTokenAsync(resourceId,ClientId, uc).Result;
this is a snippet of my AD auth code:
string authority = string.Format(CultureInfo.InvariantCulture, Instance, Tenant);
if(_authContext == null)
{
_authContext = new AuthenticationContext(authority, false, new FileCache(_cacheFile));
}
string resourceId = path to resource id;
try {
result = _authContext.AcquireTokenSilentAsync(resourceId,ClientId).Result;
accessToken = result.AccessToken;
expiresOn = result.ExpiresOn;
} catch( AggregateException exc)
{
if(ex != null && ex.ErrorCode != "failed_to_acquire_token_silently")
{
throw;
}
}
if(accessToken == null || expiresOn <= DateTimeOffset.UtcNow.AddMinutes(5))
{
UserCredential uc = new UserCredential();
uc = new UserCredential("hardcoded username for testing");
result = _authContext.AcquireTokenAsync(resourceId,ClientId, uc).Result;
accessToken = result.AccessToken;
expiresOn = result.ExpiresOn;
}
I have tried hardcoding the UserCredential already and the app pool account in the IIS has the permission.
in my web.config, i added this:
<system.net>
<defaultProxy useDefaultCredentials="true">
<proxy bypassonlocal="True" proxyaddress="......" />
</defaultProxy>
</system.net>
i have also tried setting up a WebProxy in the App Start method of my Glabal.asax but it did not help.Is it a problem with the code or a config issue in the IIS?
Related
I have a .NET MVC Core 3.1 Webapp running on azure. This webapp is with SSO against Azure AD and is consumming powerbi API and graph API in delegated mode.
All was working fine but now I regularly have failed_to_acquire_token_silently Exceptions when AcquireTokenSilentAsync is triggered. This is not 100% of the times and happears to me a bit randomly.
Let me try to extract what I think are the most relevant code parts.
Startup.cs / ConfigureServices:
services.AddAuthentication("Azures").AddPolicyScheme("Azures", "Authorize AzureAd or AzureAdBearer", options =>
{
options.ForwardDefaultSelector = context =>
{
....
};
})
.AddJwtBearer(x =>
{
.....
})
// For browser access
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
Startup.cs / ConfigureTokenHandling:
private void ConfigureTokenHandling(IServiceCollection services)
{
if (Configuration["AuthWithAppSecret:ClientSecret"] != "")
{
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.ResponseType = Configuration["AuthWithAppSecret:ResponseType"];
options.ClientSecret = Configuration["AuthWithAppSecret:ClientSecret"];
options.Events = new OpenIdConnectEvents
{
OnAuthorizationCodeReceived = async ctx =>
{
HttpRequest request = ctx.HttpContext.Request;
//We need to also specify the redirect URL used
string currentUri = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path);
//Credentials for app itself
var credential = new ClientCredential(ctx.Options.ClientId, ctx.Options.ClientSecret);
//Construct token cache
ITokenCacheFactory cacheFactory = ctx.HttpContext.RequestServices.GetRequiredService<ITokenCacheFactory>();
TokenCache cache = cacheFactory.CreateForUser(ctx.Principal);
var authContext = new AuthenticationContext(ctx.Options.Authority, cache);
string resource = Configuration["PowerBI:PowerBiResourceUrl"];
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(
ctx.ProtocolMessage.Code, new Uri(currentUri), credential, resource);
//Tell the OIDC middleware we got the tokens, it doesn't need to do anything
ctx.HandleCodeRedemption(result.AccessToken, result.IdToken);
}
};
});
}
}
A controller is like this :
public class ProjectsController : BaseController
{
private readonly ITokenCacheFactory _tokenCacheFactory;
public ProjectsController(MyContext context, IConfiguration configuration, ITokenCacheFactory tokenCacheFactory)
{
_context = context;
_tokenCacheFactory = tokenCacheFactory;
_configuration = configuration;
}
Later triggered by the controller:
static public async Task<string> GetAccessTokenAsync2(IConfiguration _configuration, ITokenCacheFactory _tokenCacheFactory, ClaimsPrincipal User, string resURL, Uri redirectURI)
{
string authority = _configuration["AzureAd:Authority"];
string clientId = _configuration["AzureAd:ClientId"];
string clientSecret = _configuration["AuthWithAppSecret:ClientSecret"];
var cache = _tokenCacheFactory.CreateForUser(User);
var authContext = new AuthenticationContext(authority, cache);
var credential = new ClientCredential(clientId, clientSecret);
var userId = User.GetObjectId();
AuthenticationResult result;
try
{
result = await authContext.AcquireTokenSilentAsync(
resURL,
credential,
new UserIdentifier(userId, UserIdentifierType.UniqueId));
}
catch (AdalException ex)
{
mylog.Info("GetAccessTokenAsync - Adal Ex:" + ex.ErrorCode);
if (ex.ErrorCode == "failed_to_acquire_token_silently")
{
// There are no tokens in the cache.
try
{
PlatformParameters param = new PlatformParameters();
result = await authContext.AcquireTokenAsync(resURL, clientId, redirectURI, param, new UserIdentifier(userId, UserIdentifierType.UniqueId));
}
catch (Exception e)
{
mylog.Error("GetAccessTokenAsync - AcquireTokenAsync" + e.ToString());
throw e;
}
}
else
throw ex;
}
return result.AccessToken;
}
AcquireTokenAsync has been added to turn around the failed_to_acquire_token_silently issue (but it is totaly failling).
Do you have any idea why it is failing from time to time ?
Any other idea how to fix it ?
Thanks!!!
Christian
EDIT 07/04:
Here an example:
2021-04-07 15:18:24.674 +00:00 OnAuthorizationCodeReceived is triggered for user fd918ddf-fbb9-40d2-812b-b01876118f42
2021-04-07 15:18:31.675 +00:00 AcquireTokenSilentAsync - trigger exception userId 'fd918ddf-fbb9-40d2-812b-b01876118f42'
The users is authenticated against AD correctly. A code is received and few seconds later there a failed_to_acquire_token_silently exception raised.
The error failed_to_acquire_token_silently occurs when an access token cannot be found in the cache or the access token is expired.
Code sample here:
// STS
string cloud = "https://login.microsoftonline.com";
string tenantId = "331e6716-26e8-4651-b323-2563936b416e";
string authority = $"{cloud}/{tenantId}";
// Application
string clientId = "65b27a1c-693c-44bf-bf92-c49e408ccc70";
Uri redirectUri = new Uri("https://TodoListClient");
// Application ID of the Resource (could also be the Resource URI)
string resource = "eab51d24-076e-44ee-bcf0-c2dce7577a6a";
AuthenticationContext ac = new AuthenticationContext(authority);
AuthenticationResult result=null;
try
{
result = await ac.AcquireTokenSilentAsync(resource, clientId);
}
catch (AdalException adalException)
{
if (adalException.ErrorCode == AdalError.FailedToAcquireTokenSilently
|| adalException.ErrorCode == AdalError.InteractionRequired)
{
result = await ac.AcquireTokenAsync(resource, clientId, redirectUri,
new PlatformParameters(PromptBehavior.Auto));
}
}
Note that, AcquireTokenSilent does not need to be called in the Client credentials flow (when the application acquires token without a
user, but in its own name)
But you use client credentials flow in your code, you could get access token via AcquireTokenAsync.
clientCredential = new ClientCredential(clientId, appKey);
AuthenticationContext authenticationContext =
new AuthenticationContext("https://login.microsoftonline.com/<tenantId>");
AuthenticationResult result =
await authenticationContext.AcquireTokenAsync("https://resourceUrl",
clientCredential);
I have hosted wcf service in worker role on http port and i iopen that port onStart method as given bellow in code
public override bool OnStart()
{
try
{
ServicePointManager.DefaultConnectionLimit = 12;
CreateServiceHost();
return base.OnStart();
}
catch (Exception)
{
return true;
}
// Set the maximum number of concurrent connections
}
private ServiceHost serviceHost;
private void CreateServiceHost()
{
//serviceHost = new ServiceHost(typeof(Service));
//WebHttpBinding binding = new WebHttpBinding(WebHttpSecurityMode.None);
//RoleInstanceEndpoint externalEndPoint =
// RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["ServiceEP"];
//string endpoint = String.Format("http://{0}/Service",
// externalEndPoint.IPEndpoint);
//serviceHost.AddServiceEndpoint(typeof(IService), binding, endpoint);
//serviceHost.Open();
var endpoint = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["ServiceEP"];
var ub = new UriBuilder();
ub.Host = endpoint.IPEndpoint.Address.ToString();
ub.Port = endpoint.IPEndpoint.Port;
ub.Scheme = "http";
ub.Path = "Service";
var host = new ServiceHost(typeof(Service), ub.Uri);
host.AddDefaultEndpoints();
host.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true });
foreach (var e in host.Description.Endpoints)
{
((BasicHttpBinding)e.Binding).HostNameComparisonMode = HostNameComparisonMode.Exact;
}
host.Open();
//Trace.WriteLine("WCF Hello service running...");
}
when i try to access method defined in service i got bellow error
The remote server returned an error: (400) Bad Request.
I am trying to connect a target host, which requires a username and password for primitive authentication from a proxy server that also have its own host, username and password.
Updated
The use case is something like this
My System connects with internet using proxy assume proxy.mydomain.com
And my proxy uses authentication to connect and authenticatuion is done using username and password
Username : MyDomain\myusername
Password : Password
Ok now the API or service which I want to connect is using Preemptive authentication I have some code snippet form API Docs :
final HttpState state = client.getState();
final HttpClientParams params = client.getParams();
myCreds = new UsernamePasswordCredentials(userName, userPassword);
System.out.println("Created credentials for " + myCreds.getUserName());
// Apply the credentials to the httpclient for preemptive authentication.
params.setAuthenticationPreemptive(true);
state.setCredentials(AuthScope.ANY, myCreds);
I tried with all apche clients from 4.1 to 4.3 but no solution at all.
I can set one credential but not getting how can both set together to a HttpClient ?
Below is my Code :
CloseableHttpResponse response;
HttpHost myHost=new HttpHost("172.17.8.192", 443, "https");
CredentialsProvider myServerCredsProvider;
if (userName.length() > 0 && userPassword.length() > 0)
{
myServerCredsProvider = new BasicCredentialsProvider();
myServerCredentials = new UsernamePasswordCredentials(userName, userPassword);
logger.info("Username : "+userName+" Password : "+userPassword);
myServerCredsProvider.setCredentials(
new AuthScope(myHost.getHostName(),myHost.getPort()),
myServerCredentials);
logger.info("After Creds Provider");
client = HttpClients.custom()
.setDefaultCredentialsProvider(myServerCredsProvider).build();
}
HttpHost proxy = new HttpHost("proxy.mycompany.com", 8080);
UsernamePasswordCredentials poxyAuthDetails=new UsernamePasswordCredentials("MyDomain//username", "password");
CredentialsProvider proxyProvider=new BasicCredentialsProvider();
proxyProvider.setCredentials(new AuthScope(proxy.getHostName(),proxy.getPort()), poxyAuthDetails);
RequestConfig config = RequestConfig.custom()
.setProxy(proxy)
.build();
HttpGet getServerDetailsUrl=new HttpGet(BaseURI+DominoServerAddress+API_BASE_URI);
getServerDetailsUrl.setConfig(config);
try {
response=client.execute(getServerDetailsUrl);
try {
HttpEntity entity = response.getEntity();
logger.info("----------------------------------------");
logger.info(response.getStatusLine());
if (entity != null) {
logger.info("Response content length: " + entity.getContentLength());
}
EntityUtils.consume(entity);
} finally {
response.close();
}
} catch (IOException ex) {
isAuthenticate=false;
logger.info("Exception Occured "+ex.getMessage());
}
I am getting exception while trying to execute :
HTTP/1.1 407 Proxy Authorization Required
In following this tutorial (modifying it to use an application-based auth string rather than their user model), have the following TokenValidationAttribute defined and set this attribute on WebAPI controllers in order to verify that the API request came within my web application:
public class TokenValidationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
string token;
try
{
token = actionContext.Request.Headers.GetValues("Authorization-Token").First();
}
catch (Exception)
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
{
Content = new StringContent("Missing Authorization-Token")
};
return;
}
try
{
var crypto = new SimpleCrypto.PBKDF2(); // type of encryption
var authPart = ConfigurationManager.AppSettings["AuthorizationTokenPart"];
var authSalt = GlobalVariables.AuthorizationSalt;
var authToken = GlobalVariables.AuthorizationToken;
if (authToken == crypto.Compute(authPart, authSalt))
{
// valid auth token
}
else
{
// invalid auth token
}
//AuthorizedUserRepository.GetUsers().First(x => x.Name == RSAClass.Decrypt(token));
base.OnActionExecuting(actionContext);
}
catch (Exception ex)
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
{
Content = new StringContent("Unauthorized User")
};
return;
}
}
}
In my login class, I have the following method defined that returns a User object if valid:
private User IsValid(string username, string password)
{
var crypto = new SimpleCrypto.PBKDF2(); // type of encryption
using (var db = new DAL.DbContext())
{
var user = db.Users
.Include("MembershipType")
.FirstOrDefault(u => u.UserName == username);
if (user != null && user.Password == crypto.Compute(password, user.PasswordSalt))
{
return user;
}
}
return null;
}
As you can see, the user login validation method doesn't make a WebAPI call that would be to ~/api/User (that part works).
1) How do I generate a request with with auth token (only site-generated API requests are valid)? These could be direct API calls from code-behind, or JavaScript-based (AngularJS) requests to hydrate some objects.
2) I'm not entirely clear on what base.OnActionExecuting(actionContext); . What do I do if the token is valid/invalid?
i think the best practices to send authorization header is by added it on request header
request.Headers.Add("Authorization-Token",bla bla bla);
you can create webrequest or httprequest
maybe you should start from http://rest.elkstein.org/2008/02/using-rest-in-c-sharp.html
or http://msdn.microsoft.com/en-us/library/debx8sh9%28v=vs.110%29.aspx.
in my opinion in order to create proper login security and request you should apply a standard such as openid or oauth
cheers
I did something like this, LoginSession contains my token and is static (in my case its a shared service (not static))
public HttpClient GetClient()
{
var client = new HttpClient
{
Timeout = new TimeSpan(0, 0, 2, 0),
BaseAddress = new Uri(GetServiceAddress())
};
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
if (LoginSession.Token != null)
{
client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", String.Format("Bearer {0}", LoginSession.Token.AccessToken));
}
return client;
}
notice this line:
client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", String.Format("Bearer {0}", LoginSession.Token.AccessToken));
I've written a self-hosted WCF service using WSHttpBindings and I'm trying to implement message-level security using certificates I've generated myself. Unfortunately I'm getting a buried exception (via the Service Trace Viewer) stating "The credentials supplied to the package were not recognized."
A couple notes:
This has to be done in code, not
in configuration
(Server/Client)Cert are certificates
that are in the local machine store
with accessible private keys to my
user while debugging.
I've googled the hell out of this and
found a good resource for setting up
WCF message based security here
I'm not sure what I'm missing. Most of this stuff seems straight forward except for creating the endpoint identities. It fails with the same message whether I use DnsEndpointIdentities, cert based ones, or no identities at all.
Can anyone point me in the right direction?
Server side:
var binding = new WSHttpBinding
{
Security =
{
Mode = SecurityMode.Message,
Message =
{
ClientCredentialType = MessageCredentialType.Certificate,
AlgorithmSuite = SecurityAlgorithmSuite.Basic256Sha256Rsa15
}
}
};
_host = new ServiceHost(this)
{
Credentials =
{
ServiceCertificate =
{
Certificate = ServiceCert
},
ClientCertificate =
{
Certificate = ClientCert,
Authentication =
{
TrustedStoreLocation = StoreLocation.LocalMachine,
RevocationMode = X509RevocationMode.NoCheck,
CertificateValidationMode = X509CertificateValidationMode.PeerOrChainTrust
}
}
}
};
var address = new Uri(string.Format(#"http://serviceaddress"));
var ep = _host.AddServiceEndpoint(typeof (IService), binding, address);
ep.Address = new EndpointAddress(address, EndpointIdentity.CreateX509CertificateIdentity(ServiceCert));
_host.Open();
Client side:
var binding = new WSHttpBinding
{
Security =
{
Mode = SecurityMode.Message,
Message =
{
ClientCredentialType = MessageCredentialType.Certificate,
AlgorithmSuite = SecurityAlgorithmSuite.Basic256Sha256Rsa15
}
}
};
var address = new Uri(#"http://serviceaddress");
var endpoint = new EndpointAddress(address, EndpointIdentity.CreateX509CertificateIdentity(ServerCert));
var channelFactory = new ChannelFactory<IService>(binding, endpoint)
{
Credentials =
{
ServiceCertificate =
{
DefaultCertificate = ServerCert,
Authentication =
{
RevocationMode = X509RevocationMode.NoCheck,
TrustedStoreLocation = StoreLocation.LocalMachine,
CertificateValidationMode = X509CertificateValidationMode.PeerOrChainTrust
}
},
ClientCertificate =
{
Certificate = ClientCert
}
}
};
var channel = channelFactory.CreateChannel();
this msdn article helped tremendously. I think the root of the problem was setting the following message security parameters to false:
httpBinding.Security.Message.NegotiateServiceCredential = false;
httpBinding.Security.Message.EstablishSecurityContext = false;
So now the overall code for the server side looks more like:
var httpBinding = new WSHttpBinding(SecurityMode.Message);
httpBinding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
httpBinding.Security.Message.NegotiateServiceCredential = false;
httpBinding.Security.Message.EstablishSecurityContext = false;
var httpUri = new Uri("http://serviceaddress");
_host = new ServiceHost(this, httpUri);
_host.Credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.TrustedPeople, X509FindType.FindByThumbprint, serverThumbprint);
_host.Credentials.ClientCertificate.Authentication.RevocationMode = X509RevocationMode.NoCheck;
_host.Credentials.ClientCertificate.Authentication.TrustedStoreLocation = StoreLocation.LocalMachine;
_host.AddServiceEndpoint(typeof(IMetaService), httpBinding, httpUri);
_host.Open();
and the client side:
var httpBinding = new WSHttpBinding(SecurityMode.Message);
httpBinding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
httpBinding.Security.Message.NegotiateServiceCredential = false;
httpBinding.Security.Message.EstablishSecurityContext = false;
var httpUri = new Uri("http://serviceaddress");
var httpEndpoint = new EndpointAddress(httpUri, EndpointIdentity.CreateDnsIdentity("name of server cert"));
var newFactory = new ChannelFactory<IMetaService>(httpBinding, httpEndpoint);
newFactory.Credentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.TrustedPeople, X509FindType.FindByThumbprint, "client certificate thumbprint");
newFactory.Credentials.ServiceCertificate.SetDefaultCertificate(StoreLocation.LocalMachine, StoreName.TrustedPeople, X509FindType.FindByThumbprint, "server certificate thumbprint");
var channel = newFactory.CreateChannel();