This is my last stop in trying to get this thing to work.
I have created a WCF service in .net/C# and it is hosted in IIS (port 80) on an amazon EC2 instance. The service uses OAuth2 (GoogleWebAuthorizationBroker.AuthorizeAsync) to authorize the service to read/write files to google drive. The client app uses POST messages to call the services add method that triggers the auth and then adds a file to google drive.
My problem, and I've left no stone unturned, is that when I run the service on my dev machine, (visual studios created IIS express instance), the client has no problem when the POST is sent getting a proper successful response and adding the file to Google Drive (the file does transfer to google drive). However, when I upload the service to IIS on my Amazon EC2 instance and change the connection strings on the client to point to ec2 instance, the service returns a (400) Bad Request from the service.
I know the service works with everything except the google authorization and google drive file upload, because i removed this and the POST works, but when the google auth and google drive file upload are retained, I get a 400 bad request...
In the google dev console I have the redirect uri's setup to:
http://localhost:3537
http://localhost/authorize/
http://localhost:80/authorize/
http://localhost:80/TimesheetService.svc/authorize/
http://localhost/TimesheetService.svc/authorize/
http://localhost/authorize/
C# Using Namespaces:
using Google.Apis.Auth.OAuth2;
using Google.Apis.Drive.v2;
using Google.Apis.Drive.v2.Data;
using Google.Apis.Services;
using Google.Apis.Util.Store;
EC2 IIS setup:
The WCF service is hosted in IIS as a new site and points to localhost port 80
Here is the OAuth code that is failing on EC2 but not dev:
private static String ACTIVITY_ID = "REMOVED";
static String CLIENT_ID = "REMOVED";
static String CLIENT_SECRET = "REMOVED";
static String APP_USER_AGENT = "ApplicationB";
static String[] SCOPES = new [] { DriveService.Scope.Drive };
/// <summary> Returns the request initializer required for authorized requests. </summary>
private static async Task<DriveService> GetCredentials()
{
ClientSecrets secrets = new ClientSecrets
{
ClientId = CLIENT_ID,
ClientSecret = CLIENT_SECRET
};
IDataStore credentialPersistanceStore = getPersistentCredentialStore();
UserCredential credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(secrets,
SCOPES, getUserId(), CancellationToken.None, credentialPersistanceStore);
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = APP_USER_AGENT
});
return service;
}
/// <summary>
/// Returns an ID string for the current user. IDs are unique within this application.
/// </summary>
private static String getUserId()
{
// TODO: Generate a unique user ID within your system for this user. The credential
// data store will use this as a key to look up saved credentials.
return "MyGoogleEmail#Gmail.com";
}
/// <summary> Returns a persistent data store for user's credentials. </summary>
private static IDataStore getPersistentCredentialStore()
{
// TODO: This uses a local file store to cache credentials. You should replace this with
// the appropriate IDataStore for your application.
return new FileDataStore("Drive.Sample.Credentals");
}
Can somebody please shine some light on the issues I'm facing revolving WCF on IIS in EC2 using google OAuth. I Cannot seem to get this working.
Related
I'm trying to achieve two things from my C# client application using Google API.
List all users of my google directory
Get metadata of all emails for each user
It appears, however, that my service account configuration is giving me issues. This is what I have done.
Created project in console.cloud.google.com
Enabled Admin SDK and Gmail API from API's and Services
Created Service Account with Domain-Wide delegation and obtained credentials.json file.
4. On admin.google.com I went to Security / API Controls and manage Domain-wide delegation. From here I added new API Client from my Service Account client id and assigned the following scopes:
https://www.googleapis.com/auth/gmail.settings.basic
https://www.googleapis.com/auth/gmail.settings.sharing
https://www.googleapis.com/auth/admin.directory.user.readonly
Finally, when I want to try to retrieve users list I get error: Expected OAuth 2 access token, login cookie or other valid authentication credential
static void Main(string[] args)
{
using (var stream =
new FileStream("..\\..\\..\\credentials.json", FileMode.Open, FileAccess.Read))
{
var credential = GoogleCredential.FromFile("..\\..\\..\\credentials.json");
var dirservice = new DirectoryService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "testbuildingnomiproductname",
});
var listReq = dirservice.Users.List();
Users allUsers = listReq.Execute();
int counter = 0;
foreach (User myUser in allUsers.UsersValue)
{
Console.WriteLine("*" + myUser.PrimaryEmail);
counter++;
}
Console.WriteLine(counter);
Console.ReadKey();
}
}
I have an ASPNET Core 2 application which I am trying to Authenticate with Azure AD using OpenId. I just have boilerplate code from selecting Single Organization Authentication in the ASPNET Core 2 templates, so no custom code. I followed the article here.
The app is not able to get metadata from the Azure AD application because of proxy. The same URL returns data if I just paste it in browser.
The error I get is:
HttpRequestException: Response status code does not indicate success: 407 (Proxy Authentication Required).
System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
IOException: IDX10804: Unable to retrieve document from: 'https://login.microsoftonline.com/my-tenant-id/.well-known/openid-configuration'.
Microsoft.IdentityModel.Protocols.HttpDocumentRetriever+d__8.MoveNext()
I have another ASPNET 4.5.2 application where I am able to perform authentication with the same Azure AD app as above after setting proxy in code like below:
System.Net.HttpWebRequest.DefaultWebProxy = new WebProxy
{
Address = new Uri("http://my-company-proxy:8080"),
Credentials = new NetworkCredential
{
UserName = "proxyusername",
Password = "proxypassword"
}
};
So Essentially my problem is to get past the Proxy Authentication in ASPNET Core 2.
I have tried Microsoft.AspNetCore.Proxy package. Its pretty much broken and doesn't work for me. Also I tried adding the Proxy entries in machine.config (which are actually not required for 4.5.2 app) but that doesn't work as well. I believe getting past a corporate proxy should be very trivial, but doesn't look like it so far.
Tratcher's comment pointed me in the right direction and I got it working, but just to help everyone with it, below is what you need to do:
builder.AddOpenIdConnect(options => options.BackchannelHttpHandler = new HttpClientHandler
{
UseProxy = true,
Proxy = new WebProxy
{
Credentials = new NetworkCredential
{
UserName = "myusername",
Password = "mypassword"
},
Address = new Uri("http://url:port")
}
});
In Full .net framework setting up a proxy is using a config setting
entry but to use an HTTP proxy in .net core ,you have to implement
IWebProxy interface.
Microsoft.AspNetCore.Proxy is proxy middleware which serves a different purpose (to setup reverse proxy) not as an http proxy .Refer this article for more details
To implement a webproxy in .net core,
public class MyHttpProxy : IWebProxy
{
public MyHttpProxy()
{
//here you can load it from your custom config settings
this.ProxyUri = new Uri(proxyUri);
}
public Uri ProxyUri { get; set; }
public ICredentials Credentials { get; set; }
public Uri GetProxy(Uri destination)
{
return this.ProxyUri;
}
public bool IsBypassed(Uri host)
{
//you can proxy all requests or implement bypass urls based on config settings
return false;
}
}
var config = new HttpClientHandler
{
UseProxy = true,
Proxy = new MyHttpProxy()
};
//then you can simply pass the config to HttpClient
var http = new HttpClient(config)
checkout https://msdn.microsoft.com/en-us/library/system.net.iwebproxy(v=vs.100).aspx
I have a service hosted in a Service Fabric cluster in Azure (not locally) and I'm trying to call a method in it using a console application on my local machine. Using WCF for communication, I have a HTTPS endpoint set up in my application on a specific port, and have configured load balancing rules for the port in the Azure portal. The cluster has 6 nodes and the application is the only one deployed on the cluster.
Have followed the ServiceFabric.WcfCalc on GitHub (link), which works on a local cluster using HTTP endpoints, but can't call a method on the service using HTTPS endpoints once it has been deployed. What do I need to do to get it working? Have tried following the example here but don't know how to configure this for HTTPS with a service on multiple nodes for a console application to access.
Thanks in advance.
EDIT Here's my client code which I am using to call the service method. I pass the fabric:/ URI into the constructor here.
public class Client : ServicePartitionClient<WcfCommunicationClient<IServiceInterface>>, IServiceInterface
{
private static ICommunicationClientFactory<WcfCommunicationClient<IServiceInterface>> communicationClientFactory;
static Client()
{
communicationClientFactory = new WcfCommunicationClientFactory<IServiceInterface>(
clientBinding: new BasicHttpBinding(BasicHttpSecurityMode.Transport));
}
public Client(Uri serviceUri)
: this(serviceUri, ServicePartitionKey.Singleton)
{ }
public Client(
Uri serviceUri,
ServicePartitionKey partitionKey)
: base(
communicationClientFactory,
serviceUri,
partitionKey)
{ }
public Task<bool> ServiceMethod(DataClass data)
{
try
{
//It hangs here
return this.InvokeWithRetry((c) => c.Channel.ServiceMethod(data));
}
catch (Exception)
{
throw;
}
}
}
When debugging my console application on my local machine, the application hangs on the InvokeWithRetry call which calls the method in my service in Service Fabric. The application does not throw any exceptions and does not return to the debugger in Visual Studio.
Make sure you run every service instance /replica with a unique url.
Make sure you call the WebHttpBinding constructor using WebHttpSecurityMode.Transport.
Make sure you register the url using the same port number (443 likely) as in you service manifest endpoint declaration.
Make sure the endpoint is configured as HTTPS.
The warning you see in Service Fabric is telling you that there is already another service registered to listen on port 443 on your nodes. This means that Service Fabric fails to spin up your service (since it throws an exception internally when it is trying to register the URL with http.sys). You can change the port for your service to something else that will not conflict with the existing service, e.g.:
<Resources>
<Endpoint Name="CalculatorEndpoint" Protocol="https" Type="Input" Port="44330" />
</Endpoints>
If you log in to Service Fabric Explorer on https://{cluster_name}.{region}.cloudapp.azure.com:19080 you should be able to see what other applications and services are running there. If you expand services all the way down to node you should be able to see the registered endpoints, including ports, for existing services.
Bonus
You can query the cluster using FabricClient for all registered endpoints
var fabricClient = new FabricClient();
var applicationList = fabricClient.QueryManager.GetApplicationListAsync().GetAwaiter().GetResult();
foreach (var application in applicationList)
{
var serviceList = fabricClient.QueryManager.GetServiceListAsync(application.ApplicationName).GetAwaiter().GetResult();
foreach (var service in serviceList)
{
var partitionListAsync = fabricClient.QueryManager.GetPartitionListAsync(service.ServiceName).GetAwaiter().GetResult();
foreach (var partition in partitionListAsync)
{
var replicas = fabricClient.QueryManager.GetReplicaListAsync(partition.PartitionInformation.Id).GetAwaiter().GetResult();
foreach (var replica in replicas)
{
if (!string.IsNullOrWhiteSpace(replica.ReplicaAddress))
{
var replicaAddress = JObject.Parse(replica.ReplicaAddress);
foreach (var endpoint in replicaAddress["Endpoints"])
{
var endpointAddress = endpoint.First().Value<string>();
Console.WriteLine($"{service.ServiceName} {endpointAddress} {endpointAddress}");
}
}}}}}
Just run that with the proper FabricClient credentials (if it is a secured cluster) and you should see it listing all endpoints for all services there. That should help you find the one that has an endpoint for :443
I'm trying to access the Google Directory using a Service Account. I've fiddled with the DriveService example to get this code:
public static void Main(string[] args)
{
var service = BuildDirectoryService();
var results = service.Orgunits.List(customerID).Execute();
Console.WriteLine("OrgUnits");
foreach (var orgUnit in results.OrganizationUnits)
{
Console.WriteLine(orgUnit.Name);
}
Console.ReadKey();
}
static DirectoryService BuildDirectoryService()
{
X509Certificate2 certificate = new X509Certificate2(SERVICE_ACCOUNT_PKCS12_FILE_PATH, "notasecret",
X509KeyStorageFlags.Exportable);
var provider = new AssertionFlowClient(GoogleAuthenticationServer.Description, certificate)
{
ServiceAccountId = SERVICE_ACCOUNT_EMAIL,
Scope = DirectoryService.Scopes.AdminDirectoryOrgunit.GetStringValue()
};
var auth = new OAuth2Authenticator<AssertionFlowClient>(provider, AssertionFlowClient.GetState);
return new DirectoryService(new BaseClientService.Initializer()
{
Authenticator = auth,
ApplicationName = "TestProject1",
});
}
When I run it, I get
ArgumentException: Precondition failed.: !string.IsNullOrEmpty(authorization.RefreshToken)
I'm going round in circles in the Google documentation. The only stuff I can find about RefreshTokens seems to be for when an individual is authorizing the app and the app may need to work offline. Can anyone help out or point me in the direction of the documentation that will, please.
Service Account authorization actually do not return Refresh Token - so this error makes sense. Do you know where this is coming from?
I am not too familiar with the .NET client library but having the full error trace would help.
As a longshot - The error might be a bad error -
Can you confirm that you've enabled the Admin SDK in the APIs console for this project
Can you confirm that you whitelisted that Client ID for the service account in the domain you are testing with (along with the Admin SDK scopes)
The above code will work if you replace the provider block with:
var provider = new AssertionFlowClient(GoogleAuthenticationServer.Description, certificate)
{
ServiceAccountId = SERVICE_ACCOUNT_EMAIL,
Scope = DirectoryService.Scopes.AdminDirectoryOrgunit.GetStringValue(),
ServiceAccountUser = SERVICE_ACCOUNT_USER //"my.admin.account#my.domain.com"
};
I had seen this in another post and tried it with my standard user account and it didn't work. Then I read something that suggested everything had to be done with an admin account. So, I created a whole new project, using my admin account, including creating a new service account, and authorising it. When I tried it, it worked. So, then I put the old service account details back in but left the admin account in. That worked, too.
I've been following the steps to make Windows 8 Store app get an ACS token as described here:
Does the WebAuthenticationBroker work in Windows 8 Metro App post Release Candidate
However, the ResponseData property of my WebAuthenticationResult object only contains the callback uri as specified in ACS, and contains no token information. Below my code.
Authentication method of Windows 8 client
private async void Authenticate()
{
WebAuthenticationResult webAuthenticationResult = await WebAuthenticationBroker.AuthenticateAsync(
WebAuthenticationOptions.None,
new Uri("https://myACSnamespace.accesscontrol.windows.net:443/v2/wsfederation?wa=wsignin1.0&wtrealm=http://localhost:12714/"),
new Uri("http://mypublicIPaddress:80/WebAppMVCAPI/api/federation/end"));
My Relying Party application Return URL is set to http://mypublicIPaddress:80/WebAppMVCAPI/api/federation/en
My controller on the web application is programmed as follows:
public class FederationController : ApiController
{
protected virtual string ExtractBootstrapToken()
{
return "Hello World";
//return HttpContext.Current.User.BootstrapToken();
}
[HttpGet]
public string Get()
{
return "Hello Get World";
}
[HttpPost]
public HttpResponseMessage Post()
{
var response = this.Request.CreateResponse(HttpStatusCode.Redirect);
response.Headers.Add("Location", "/WebAppMVCAPI/api/federation/end?acsToken=" + ExtractBootstrapToken());
return response;
}
}
}
As you can imagine, the web application is running on my IIS Server and listening on port 80. My router is configured to forward incoming requests as needed, and when I launch my web application in visual studio I have access to the application from an internet host.
The idea is to have the Windows 8 store app get a token from ACS with a Facebook login. When I launch the win8 client, the application shows a Facebook login page. I log in with my credentials successfully. However, when I look at the webauthenticationresult.responsedata property, I only see the callback uri. Also, I don't see any log in my firewall that ACS tried to post something to my callback uri.
Your replying party application return URL should be
http://mypublicIPaddress:80/WebAppMVCAPI/api/federation
not
http://mypublicIPaddress:80/WebAppMVCAPI/api/federation/end