Xero API Allows connection but fails to redirect back, has an uncaughtreferenceerror: fbq is not defined - xero-api

Upon running the program I am redirected to sign in with xero. Once I sign in I am able to choose an organization to allow access to the app
Upon clicking allow access I get redirected to the default "This site can't be reached" error page.
If I look at the console output when I click the button, for a few seconds an "uncaught reference error: fbq is not defined" is shown. Unfortunately it goes away before I can click on it.
Here is some of the relevant code:
void LoginToXero()
{
var xeroLoginUri = XeroService.GetLoginUri();
OpenBrowser(xeroLoginUri);
var listener = new HttpListener();
listener.Prefixes.Add(XeroService.CallbackUri);
listener.Start();
Console.WriteLine("Waiting for the browser to callback from Xero login page...");//Logs
var context = listener.GetContext();//Does not progress past here
//...
}
public static class XeroService
{
public static string CallbackUri => "xxxxxxxxxxxxx";
static string xeroState = Guid.NewGuid().ToString();
static string oAuth2Token = "";
static XeroClient xeroClient = new XeroClient(new XeroConfiguration
{
ClientId = "XXXXXXXXXXXXXX",
ClientSecret = "XXXXXXXXXXXXXXXXXXXX",
Scope = "openid payroll.employees",
CallbackUri = new Uri(CallbackUri)
});
public static string GetLoginUri()
{
xeroClient.xeroConfiguration.State = xeroState;
return xeroClient.BuildLoginUri();
}
}
Please note all sensitive data has been replaced by "XXXXXXXXX"
I have tested both localhost callback URI's (with specified ports) and custom ones that redirect to localhost via the host file on my machine
I have also tried running it on Windows 11 and Windows 10, both with the firewall enabled and then with it disabled
Any help would be greatly appreciated

The problem was that the listener and the App was set up for https, changing it to http and making sure there was an explicit port resolved the issue

Related

ASP.Net Core Authentication Providers

I created an ASP.Net Core 2.0 MVC using authentication providers as described here: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/social/
On localhost (i.e. when run via Visual Studio 2017) all works well. However, after deploying to Azure I found that the login providers stopped working (despite my setting up appropriate callback URIs; e.g. for Google I have https://localhost:44357/signin-google but also https://mysite.azurewebsites.net/signin-google, https://example.com/signin-google, and https://www.example.com/signin-google (as well as having setup the example.com domain and its www subdomain in Azure and configured SSL covering these domains). For Twitter I've changed the setup to the www subdomain only (as only 1 callback URL's allowed), and for LinkedIn I only have the domain and subdomain (i.e. I had to remove localhost; as LinkedIn only allows callback URI's under a single domain). I've also configured those keys/values which had been in my secrets.json under the Azure App Service's Application Settings.
Symptoms
On first login (aka registration), the user clicks the relevant provider's button after which new user entry appears in the AspNetUsers and AspNetUserLogins tables, and the user is directed to the page where they can associate their email. However, they're not logged in at that point; just registered. Subsequent attempts take them back to the email registration form; only clicking the Register button then returns an error message stating that the email's already registered (which is correct); but the user's still not signed in to the site.
I have the same issue with all providers; though after proving this focussed most of my ongoing on Google, just to limit the number of changing variables.
The only significant change I've made from the example was to refactor code in Startup.cs so that each provider's encapsulated in it's own method; so ConfigureServices contains:
ConfigureServicesAuthFacebook(services);
ConfigureServicesAuthGoogle(services);
ConfigureServicesAuthTwitter(services);
ConfigureServicesAuthMicrosoft(services);
ConfigureServicesAuthLinkedIn(services);
... and those methods look like this:
#region Authentication Providers
public void ConfigureServicesAuthFacebook(IServiceCollection services)
{
services.AddAuthentication().AddFacebook(x =>
{
x.AppId = Configuration["Authentication:Facebook:Id"];
x.AppSecret = Configuration["Authentication:Facebook:Secret"];
});
}
public void ConfigureServicesAuthGoogle(IServiceCollection services)
{
services.AddAuthentication().AddGoogle(x =>
{
x.ClientId = Configuration["Authentication:Google:Id"];
x.ClientSecret = Configuration["Authentication:Google:Secret"];
});
}
public void ConfigureServicesAuthTwitter(IServiceCollection services)
{
services.AddAuthentication().AddTwitter(x =>
{
x.ConsumerKey = Configuration["Authentication:Twitter:Id"];
x.ConsumerSecret = Configuration["Authentication:Twitter:Secret"];
});
}
public void ConfigureServicesAuthMicrosoft(IServiceCollection services)
{
services.AddAuthentication().AddMicrosoftAccount(x =>
{
x.ClientId = Configuration["Authentication:Microsoft:Id"];
x.ClientSecret = Configuration["Authentication:Microsoft:Secret"];
});
}
public void ConfigureServicesAuthLinkedIn(IServiceCollection services)
{
services.AddAuthentication().AddOAuth("LinkedIn", x =>
{
x.ClientId = Configuration["Authentication:LinkedIn:Id"];
x.ClientSecret = Configuration["Authentication:LinkedIn:Secret"];
x.CallbackPath = new PathString("/signin-linkedin");
x.AuthorizationEndpoint = "https://www.linkedin.com/oauth/v2/authorization";
x.TokenEndpoint = "https://www.linkedin.com/oauth/v2/accessToken";
x.UserInformationEndpoint = "https://api.linkedin.com/v1/people/~:(id,formatted-name,email-address,picture-url)";
//x.Scope = { "r_basicprofile", "r_emailaddress" };
});
}
#endregion Authentication Providers
Question
How can I debug this issue given I cannot recreate the problem on localhost. Any hints on what the issue may be?
The way it works is your user first must assoicate their Google account with the user on your system. It sounds like this is working for you.
After that is done your code should preform some kind of ExternalLoginSignInAsync however this kind of depends on how you have your system set up.
Out of the box, where IsNotAllowed is true this means the email or phone number associated with the account which needs to be confirmed has not yet been confirmed. See ASN.NET Core 2.0 Facebook authentication ExternalLoginSignInAsync Fails (IsNotAllowed)
Take a look at the AccountController method ExternalLoginConfirmation and you'll see:
var user = new ApplicationUser(model.Email) { Email = model.Email };
Assuming you're happy for those signing up with existing logon providers, amend this to:
var user = new ApplicationUser(model.Email) { Email = model.Email, EmailConfirmed = true };

Google OAuth 2.0 for desktop apps for Windows without Admin privileges

I've heard about Google's plan of modernizing OAuth interactions described here: https://developers.googleblog.com/2016/08/modernizing-oauth-interactions-in-native-apps.html
Then I was looking at the sample desktop application for Windows found here: https://github.com/googlesamples/oauth-apps-for-windows/tree/master/OAuthDesktopApp.
It's pretty simple and it was working, but once I started Visual Studio without elevated privileges (as a non-admin), I experienced that the HttpListener was not able to start because of the following error: "Access Denied".
It turned out that starting an HttpListener at the loopback address (127.0.0.1) is not possible without admin rights. However trying localhost instead of 127.0.0.1 lead to success.
I found that there is a specific command that allows HttpListener to start at the given address (and port):
netsh http add urlacl url=http://+:80/MyUri user=DOMAIN\user
But it also can be only executed with admin rights, so it's not an option.
Still localhost seems to be the best shot but OAuth 2.0 for Mobile & Desktop Apps states the following regarding this section:
See the redirect_uri parameter definition for more information about the loopback IP address. It is also possible to use localhost in place of the loopback IP, but this may cause issues with client firewalls. Most, but not all, firewalls allow loopback communication.
This is why I'm a bit suspicious to use localhost. So I'm wondering what is the recommended way of Google in this case, as I'm not intending to run our application as administrator just for this reason.
Any ideas?
You can use TcpListener for instance instead of HttpListener. It does not need elevation to listen.
The following is a modified excerpt of this sample:
https://github.com/googlesamples/oauth-apps-for-windows/tree/master/OAuthDesktopApp
// Generates state and PKCE values.
string state = randomDataBase64url(32);
string code_verifier = randomDataBase64url(32);
string code_challenge = base64urlencodeNoPadding(sha256(code_verifier));
const string code_challenge_method = "S256";
// Creates a redirect URI using an available port on the loopback address.
var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
string redirectURI = string.Format("http://{0}:{1}/", IPAddress.Loopback, ((IPEndPoint)listener.LocalEndpoint).Port);
output("redirect URI: " + redirectURI);
// Creates the OAuth 2.0 authorization request.
string authorizationRequest = string.Format("{0}?response_type=code&scope=openid%20profile&redirect_uri={1}&client_id={2}&state={3}&code_challenge={4}&code_challenge_method={5}",
authorizationEndpoint,
System.Uri.EscapeDataString(redirectURI),
clientID,
state,
code_challenge,
code_challenge_method);
// Opens request in the browser.
System.Diagnostics.Process.Start(authorizationRequest);
// Waits for the OAuth authorization response.
var client = await listener.AcceptTcpClientAsync();
// Read response.
var response = ReadString(client);
// Brings this app back to the foreground.
this.Activate();
// Sends an HTTP response to the browser.
WriteStringAsync(client, "<html><head><meta http-equiv='refresh' content='10;url=https://google.com'></head><body>Please close this window and return to the app.</body></html>").ContinueWith(t =>
{
client.Dispose();
listener.Stop();
Console.WriteLine("HTTP server stopped.");
});
// TODO: Check the response here to get the authorization code and verify the code challenge
The read and write methods being:
private string ReadString(TcpClient client)
{
var readBuffer = new byte[client.ReceiveBufferSize];
string fullServerReply = null;
using (var inStream = new MemoryStream())
{
var stream = client.GetStream();
while (stream.DataAvailable)
{
var numberOfBytesRead = stream.Read(readBuffer, 0, readBuffer.Length);
if (numberOfBytesRead <= 0)
break;
inStream.Write(readBuffer, 0, numberOfBytesRead);
}
fullServerReply = Encoding.UTF8.GetString(inStream.ToArray());
}
return fullServerReply;
}
private Task WriteStringAsync(TcpClient client, string str)
{
return Task.Run(() =>
{
using (var writer = new StreamWriter(client.GetStream(), Encoding.UTF8))
{
writer.Write("HTTP/1.0 200 OK");
writer.Write(Environment.NewLine);
writer.Write("Content-Type: text/html; charset=UTF-8");
writer.Write(Environment.NewLine);
writer.Write("Content-Length: " + str.Length);
writer.Write(Environment.NewLine);
writer.Write(Environment.NewLine);
writer.Write(str);
}
});
}
By default there is a URL pattern http://+:80/Temporary_Listen_Addresses/ which is allowed for all users (\Everyone)
You can use this as a prefix for your listener. More generally (to avoid collisions with other listeners) you should generate a URL under Temporary_Listen_Addresses (e.g. using a GUID) and use that as your listener prefix.
Unfortunately, a sysadmin can use netsh http to delete this entry or to restrict its usage to only certain users. Also, this does not appear to support listening for an HTTPS request as there is no corresponding ACL entry for port 443.
An admin can list all these permitted URL patterns using netsh http show urlacl as a command.

LibGit2Sharp: Fetching fails with "Too many redirects or authentication replays"

Here's the code I'm using to fetch:
public static void GitFetch()
{
var creds = new UsernamePasswordCredentials()
{Username = "user",
Password = "pass"};
var fetchOpts = new FetchOptions {Credentials = creds};
using (repo = new Repository(#"C:\project");)
{
repo.Network.Fetch(repo.Network.Remotes["origin"], fetchOpts);
}
}
but it fails during fetch with the following exception:
LibGit2Sharp.LibGit2SharpException: Too many redirects or authentication replays
Result StackTrace:
at LibGit2Sharp.Core.Ensure.HandleError(Int32 result)
at LibGit2Sharp.Core.Proxy.git_remote_fetch(RemoteSafeHandle remote, Signature signature, String logMessage)
at LibGit2Sharp.Network.DoFetch(RemoteSafeHandle remoteHandle, FetchOptions options, Signature signature, String logMessage)
at LibGit2Sharp.Network.Fetch(Remote remote, FetchOptions options, Signature signature, String logMessage)
I have verified that the config file has the required remote name and that git fetch works from the command line. I found that the exception originates from libgit2\src\transport\winhttp.c but I couldn't come up with a workaround/solution.
I tried #Carlos' suggestion in the following way:
public static void GitFetch()
{
var creds = new UsernamePasswordCredentials()
{Username = "user",
Password = "pass"};
CredentialsHandler credHandler = (_url, _user, _cred) => creds;
var fetchOpts = new FetchOptions { CredentialsProvider = credHandler };
using (repo = new Repository(#"C:\project");)
{
repo.Network.Fetch(repo.Network.Remotes["origin"], fetchOpts);
}
}
I could fetch from public repos on github as well as from password protected private repos on bitbucket; however, I couldn't do the same for the repositories hosted over LAN at work. Turns out they were configured in a way which does not accept UsernamePasswordCredentials provided by libgit2sharp. The following modification allowed me to fetch from repositories over LAN:
CredentialsHandler credHandler = (_url, _user, _cred) => new DefaultCredentials();
(I'm trying to find out what is the exact difference between the two; if I get further insight into it, I'll update the answer.)
The shim that should make the Credentials option work is currently buggy (and is deprecated anyway), pass a CredentialsProvider instead as a callback.
This seems to be a very common error message.
We were getting it on pushes to GitHub, because credentials were disabled for security:
https://github.blog/2020-12-15-token-authentication-requirements-for-git-operations/
We've solved it by enabling SAML SSO and doing the push outside the C# code, but perhaps using SSH keys somehow with the library or personal access tokens fixes the problem too.

How to do Active Directory authentication in Razor (cshtml)

I am doing a simple website with Razor. Currently, I have database-based authentication that works, as follows:
In _AppStart.chtml:
WebSecurity.InitializeDatabaseConnection("db_connection",
"users", "id", "username", true);
In login.cshtml page:
username = Request["username"];
password = Request["password"];
if (WebSecurity.Login(username, password, true))
{
Response.Redirect("/admin");
}
else
{
errorMessage = "Login was not successful.";
}
In protected CSHTML pages, I have the following at the top of a page:
if (!WebSecurity.IsAuthenticated)
{
Response.Redirect("/login.cshtml");
}
Everything is pretty simple and works well. Now I would like to add authentication with AD. I don't know how to do it.
I came from the Java world with many years of experience. For this simple website, I do not need MVC architecture. I need simple things similar to the above (if possible). I need to do authentication just within the login.cshtml file. I googled a lot and am unable to find a tutorial (so that I can copy and paste) for what I need.
Any pointers or help is really appreciated!
Thanks and Regards
Update: This application sits on the internal network.
Update 2: Here is the code I have after successfully implemented X3074861X's code
if (IsPost)
{
username = Request["username"];
password = Request["password"];
var domain = "domain";
var host = "host";
var port = "389";
LdapConnection ldapConnection = new LdapConnection(host + ":" + port);
try
{
// authenticate the username and password
using (ldapConnection)
{
// pass in the network creds, and the domain.
var networkCredential = new NetworkCredential(username, password, domain);
// if we're using unsecured port 389, set to false. If using port 636, set this to true.
ldapConnection.SessionOptions.SecureSocketLayer = false;
// since this is an internal application, just accept the certificate either way
ldapConnection.SessionOptions.VerifyServerCertificate += delegate { return true; };
// to force NTLM\Kerberos use AuthType.Negotiate, for non-TLS and unsecured, just use AuthType.Basic
ldapConnection.AuthType = AuthType.Basic;
// this is where the authentication occurs
ldapConnection.Bind(networkCredential);
//check local database to make sure the user is one of we allowed
if (WebSecurity.Login(username, "fixed-password, just to check whether someone is on the list of allowed people", true))
{
Response.Redirect("/admin");
}
else
{
errorMessage = "Login was not successful.";
}
}
}
catch (LdapException exception)
{
//Authentication failed, exception will dictate why
errorMessage = "Login was not successful.";
}
Some explanation. I dont have control over the AD and so I can only authenticate users against it. I still have a little local database that indicates who can access the app. Everyone with access to the app has the same rights.
Thanks and credit goes to X3074861X.
Since this is an internal application, and you're looking for something simple, I would consider writing a single class to do the Active Directory authentication. You're going to need a couple things though, in order for this to work :
A reference to System.DirectoryServices.Protocols in your project.
The IP or DNS name of your Active Directory server. We'll call it host in the code below.
The port it's running on (LDAPS will be port 636, basic LDAP will be port 389). We'll call it port in the code below.
The Domain to which your users belong. We'll call it domain in the code below.
Now that you have that, you can wire this up to check the credentials from the request against your AD instance. I would try something like this :
// the username and password to authenticate
username = Request["username"];
password = Request["password"];
// define your connection
LdapConnection ldapConnection = new LdapConnection("host:port");
try
{
// authenticate the username and password
using (ldapConnection)
{
// pass in the network creds, and the domain.
var networkCredential = new NetworkCredential(username, password, domain);
// if we're using unsecured port 389, set to false. If using port 636, set this to true.
ldapConnection.SessionOptions.SecureSocketLayer = false;
// since this is an internal application, just accept the certificate either way
ldapConnection.SessionOptions.VerifyServerCertificate += delegate { return true; };
// to force NTLM\Kerberos use AuthType.Negotiate, for non-TLS and unsecured, just use AuthType.Basic
ldapConnection.AuthType = AuthType.Basic;
// authenticate the user
ldapConnection.Bind(networkCredential);
}
catch (LdapException ldapException)
{
//Authentication failed, exception will dictate why
}
}
Also, in the same way you'd communicate an authorization issue before, the ldapException can tell you why the call failed. If you want to display custom messaging, I would check the LdapException.ErrorCode property, and maybe create a case statement of return messages based on the error codes.
Or, you could just output LdapException.Message directly to the page - either way, that will at least dictate to the user why their login didn't work.

Redirect HTTP to HTTPS in MVC4 Mobile Application

In My MVC4 Mobile application i have registration, login page and remaining pages. i would like to redirect user to HTTPS connection for all sensitive information pages like registration and login pages and HTTP to remailing pages.
I prefer you to use conditional functionality putting the class
public class RequireHttpsConditional : RequireHttpsAttribute
{
protected override void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
var useSslConfig = ConfigurationManager.AppSettings["UseSSL"];
if (useSslConfig != null)
{
if (!string.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("The requested resource can only be accessed via SSL.");
}
var request = filterContext.HttpContext.Request;
string url = null;
int sslPort;
if (Int32.TryParse(useSslConfig, out sslPort) && sslPort > 0)
{
url = "https://" + request.Url.Host + request.RawUrl;
if (sslPort != 443)
{
var builder = new UriBuilder(url) { Port = sslPort };
url = builder.Uri.ToString();
}
}
if (sslPort != request.Url.Port)
{
filterContext.Result = new RedirectResult(url);
}
}
}
}
and using this [RequireHttpsConditional] above the action result.
i have got this code somewhere in internet and is working fine for me.
in web.config appsettings use <add key="UseSSL" value="443" />
and in the controller above the action result you need put
[RequireHttpsConditional]
public ActionResult SignIn()
{
}
In IIS where you have your project right click and click "Edit Bindings" then you add a custom type https and port no 443 (you can change it)
Note this will work only in production environment. when executed locally it wont be working.
When you execute it locally you have request.Url.Host which will return you only localhost and missing your port number. so if you use it in MVC you will find error loading page for your pages where you put this code.
So this will work when you have the host assigned instead of using the localhost with a specific port number.
Within the controller actions that you wish to be HTTPS add the following code to the top of the method (of course you can simply add this to its own method and then call it):
if (!HttpContext.Request.IsSecureConnection)
{
var url = new UriBuilder(HttpContext.Request.Url);
url.Scheme = "https";
Response.Redirect(url.Uri.AbsoluteUri);
}
It is recommended though that you keep HTTPS on throughout your site to protect against a MITM attack against the auth cookie.