Authentication to FreshBooks via DotNetOpenAuth - c#

I'm trying to use OAuth for authentication for the FreshBooks API from my ASP.NET MVC C# app. Here is what I have so far:
I'm using DotNetOpenAuth here is the code I have in my controller action
if (TokenManager != null)
{
ServiceProviderDescription provider = new ServiceProviderDescription();
provider.ProtocolVersion = ProtocolVersion.V10a;
provider.AccessTokenEndpoint = new MessageReceivingEndpoint ("https://myfbid.freshbooks.com/oauth/oauth_access.php", DotNetOpenAuth.Messaging.HttpDeliveryMethods.PostRequest);
provider.RequestTokenEndpoint = new DotNetOpenAuth.Messaging.MessageReceivingEndpoint("https://myfbid.freshbooks.com/oauth/oauth_request.php", DotNetOpenAuth.Messaging.HttpDeliveryMethods.PostRequest);
provider.UserAuthorizationEndpoint = new DotNetOpenAuth.Messaging.MessageReceivingEndpoint("https://myfbid.freshbooks.com/oauth/oauth_authorize.php", DotNetOpenAuth.Messaging.HttpDeliveryMethods.GetRequest);
provider.TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() };
var consumer = new WebConsumer(provider, TokenManager);
var response = consumer.ProcessUserAuthorization();
if (response != null)
{
this.AccessToken = response.AccessToken;
}
else
{
// we need to request authorization
consumer.Channel.Send(consumer.PrepareRequestUserAuthorization(
new Uri("http://localhost:9876/home/testoauth/"), null, null));
}
}
The TokenManager is the same class that is provided with the DotNetOpenAuth sample, I've set my consumer secret that FreshBooks gave me.
On the consumer.Channel.Send(consumer.PrepareRequestUserAuthorization(...)) I've got the following exception:
"The remote server returned an error: (400) Bad Request.".
Am I doing this correctly? Based on FreshBooks documentation and DotNetOpenAuth samples that should work correctly.
Is there a simpler way to authenticate with OAuth, as DotNetOpenAuth is a bit huge for simply using OAuth authentication?

if you want to use DotNetOpenAuth you need to make sure that:
you use signature method "PLAINTEXT"
and use PlaintextSigningBindingElement as TamperProtectionElements
something like this works for me:
public static readonly ServiceProviderDescription ServiceDescription = new ServiceProviderDescription
{
ProtocolVersion = ProtocolVersion.V10a,
RequestTokenEndpoint = new MessageReceivingEndpoint(oAuthBase + "/oauth_request.php", HttpDeliveryMethods.PostRequest),
UserAuthorizationEndpoint = new MessageReceivingEndpoint(oAuthBase + "/oauth_authorize.php", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
AccessTokenEndpoint = new MessageReceivingEndpoint(oAuthBase + "/oauth_access.php", HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new PlaintextSigningBindingElement() }
};
public static void RequestAuthorization(WebConsumer consumer)
{
if (consumer == null)
{
throw new ArgumentNullException("consumer");
}
var extraParameters = new Dictionary<string, string> {
{ "oauth_signature_method", "PLAINTEXT" },
};
Uri callback = Util.GetCallbackUrlFromContext();
var request = consumer.PrepareRequestUserAuthorization(callback, extraParameters, null);
consumer.Channel.Send(request);
}

You could try using my open source OAuth Library. It's extremely simple to use and get going. I have a sample project that's available in the download that connects to Google, Twitter, Yahoo and Vimeo. I've intentionally kept the code very simple so it's easy to understand.
OAuth C# Library
I've not used FreshBooks, but it should be a simple matter of changing the url for one of the providers in the sample application and of course setting up provider specific keys etc.

Related

unable to post to AXL endpoint

My goal is to be able to post and retrieve from the endpoint which uses a SOAP based API
structure of my project
I generated a client with the WSDL file to target cucm 11.5, then
I followed the example on github by creating all the classes and interfaces as done on the repo
thirdly, my solution consist of two project a class library and a console project, the class library contains the generated client from the WSDL file and the console project consist of the class and interfaces to interact with the class library project
I have the following class to perform an operation
public class TestAxl
{
public void CreateUsers()
{
var axlClient = new AxlClient(new AxlClientConfiguration
{
Server = "Ip to the publish server",
User = "administrator",
Password = "password provided"
});
var addUserResult = axlClient.ExecuteAsync(async client =>
{
var userId = Guid.NewGuid().ToString();
var request = new AddUserReq
{
user = new XUser
{
userid = userId,
userIdentity = userId,
password = "P#ssw0rd",
firstName = "test",
lastName = "test"
}
};
var response = await client.addUserAsync(request);
return response.addUserResponse1.#return;
});
}
}
and i call it from the main class like so
class Program
{
static void Main(string[] args)
{
var letsDoSomeTesting = new TestAxl();
try
{
letsDoSomeTesting.CreateUsers();
}
catch (Exception e)
{
Console.WriteLine("The following is the exceeption from calling final class ", e.Message);
}
}
}
when i try to run the console project it starts and exit with 0,
then i go back to CUCM sandbox environment and nothing has changed, what could be the possible cause of this operation not working
FYI: Runtime netCore 3.1
I was able to get a sample project together including AXL/addUser, with DotNet Core 3.1 on Linux: https://github.com/CiscoDevNet/axl-dotnet-samples
This is the main section:
// Create a custom binding so we can allow the client to use cookies with AXL
BasicHttpsBinding binding = new BasicHttpsBinding();
binding.AllowCookies = true;
// Specify the CUCM AXL API location for the SOAP client
EndpointAddress address = new EndpointAddress( $"https://{ System.Environment.GetEnvironmentVariable( "CUCM_ADDRESS" ) }:8443/axl/" );
//Class generated from AXL WSDL
AXLPortClient client = new AXLPortClient( binding, address );
// To disable HTTPS certificate checking, uncomment the below lines
// NOT for production use!
// client.ChannelFactory.Credentials.ServiceCertificate.SslCertificateAuthentication = new X509ServiceCertificateAuthentication
// {
// CertificateValidationMode = X509CertificateValidationMode.None,
// RevocationMode = X509RevocationMode.NoCheck
// };
// client.ChannelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
// client.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
// Incantation to force alternate serializer reflection behaviour due to complexities in the AXL schema
// See https://github.com/dotnet/wcf/issues/2219
MethodInfo method = typeof( XmlSerializer ).GetMethod( "set_Mode", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static );
method.Invoke( null, new object[] { 1 } );
// Base64 encode AXL username/password for Basic Auth
var encodedUserPass = Convert.ToBase64String( Encoding.ASCII.GetBytes(
System.Environment.GetEnvironmentVariable( "CUCM_USERNAME" ) + ":" +
System.Environment.GetEnvironmentVariable( "CUCM_PASSWORD" )
) );
// Incantation to create and populate a Basic Auth HTTP header
// This must be done to force SoapCore to include the Authorization header on the first attempt
// rather than in challenge/response fashion
HttpRequestMessageProperty requestProperty = new HttpRequestMessageProperty();
requestProperty.Headers[ "Authorization" ] = "Basic " + encodedUserPass;
// Creating context block apparently allows attaching custom HTTP headers to the request
var scope = new OperationContextScope( client.InnerChannel );
OperationContext.Current.OutgoingMessageProperties[ HttpRequestMessageProperty.Name ] = requestProperty;
//Create the request object
AddUserReq addUserReq = new AddUserReq();
addUserReq.user = new XUser();
addUserReq.user.lastName = "TestUser";
addUserReq.user.userid = "testUser";
addUserReq.user.password = "Cisco!1234";
string userPkid = "";
//Try the addUser request
try
{
addUserResponse addUserResp = await client.addUserAsync( addUserReq );
userPkid = addUserResp.addUserResponse1.#return;
}
catch ( Exception ex )
{
Console.WriteLine( $"\nError: addUser: { ex.Message }" );
Environment.Exit( -1 );
}
A few notes:
SoapCore generates elements with default values when it can, e.g. nil for string elements. This causes a problem with <addUser>, as the <customerName> element should only be sent to HCS CUCMs. A modification to the AXLSoap.xsd before running svcutil was able to workaround it:
sed -i 's/name=\"customerName\" nillable=\"true\"/name=\"customerName\" nillable=\"false\"/g' schema/AXLSoap.xsd
Requests will fail due to HTTPS certification validation of the CUCM self-signed certificate, unless it is installed to the OS CA trust store or disabled (see the commented section in the code above)
The following curious code was required to avoid a "Compiling JScript/CSharp scripts is not supported" error on making a request (per here):
MethodInfo method = typeof( XmlSerializer ).GetMethod( "set_Mode", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static );
method.Invoke( null, new object[] { 1 } );

OpenID login in C# desktop application?

Im newbie in C#, I have been asked to implement the OpenID login with provided username and password and get the bearer token.
We achieved this in Java code snipped is as below.(don't know how do same in C#)
public AccessTokenResponse getToken() throws IOException, ParseException, Exception {
StringBuilder query = new StringBuilder("");
ClientCredentialsGrant clientAuth = new ClientCredentialsGrant();
Map<String, String> parameters = clientAuth.toParameters();
for (String key : parameters.keySet()) {
query.append(key).append("=").append(parameters.get(key)).append("&");
}
ClientID clientID = new ClientID(config.getProperty(ConfigConstants.USERNAME));
Secret secret = new Secret(config.getPassword());
ClientSecretBasic csb = new ClientSecretBasic(clientID, secret);
//logger.info("URL:" + this.getOpenIDMetadata(config.getProperty(ConfigConstants.DOMAIN)).getTokenEndpointURI().toString());
TokenRequest tokenRequest = new TokenRequest(this.getOpenIDMetadata(config.getProperty(ConfigConstants.DOMAIN)).getTokenEndpointURI(), csb, clientAuth);
HTTPRequest request = tokenRequest.toHTTPRequest();
HTTPResponse httpResponse = request.send();
accessToken = AccessTokenResponse.parse(httpResponse);
return accessToken;
}
We have a server which is an openid provider and this is how get the OIDCProviderMetadata
private OIDCProviderMetadata getOpenIDMetadata(String domain) throws Exception {
if (this.metadata == null) {
String confURL = this.serverURL+ config.getIDP() + domain + "/authn/.well-known/openid-configuration";
HTTPRequest configRequest = new HTTPRequest(HTTPRequest.Method.GET, new URL(confURL));
HTTPResponse configResponse = configRequest.send();
metadata = OIDCProviderMetadata.parse(configResponse.getContent());
}
return metadata;
}
Question is how to implement same thing in C#?
I did some research around this, found there is/are official OpendID implementation in C# i.e.
https://github.com/IdentityModel/IdentityModel.OidcClient2
but did not find any samples which will help me to implement likewise in Java implementation i have. All examples demonstrate do openid login in browser or mobiles devices i don't want that, I want similar implementation as we have in Java(above).
Any help would be appreciated or code snipped would be more helpful.

Linq2Twitter - 401 : bad authentication data

I have been working with Linq2Twitter (v. 2), using the Search API and
I wanted to switch to the Stream API. I updated to v. 3 but since then I don't manage to authenticate anymore. I don't think the Stream API or the version could be the problem, because I've tried to go back to the previous version, previous authentication methods, and it doesn't work anymore either. I get a 401 : bad authentication data.
So, here is my current code :
var auth = new SingleUserAuthorizer
{
CredentialStore = new SingleUserInMemoryCredentialStore()
{
ConsumerKey = ConfigurationManager.AppSettings["twitterConsumerKey"],
ConsumerSecret = ConfigurationManager.AppSettings["twitterConsumerSecret"],
OAuthToken = ConfigurationManager.AppSettings["twitterOAuthToken"],
AccessToken = ConfigurationManager.AppSettings["twitterAccessToken"]
}
};
TwitterContext _twitterCtx = new TwitterContext(auth);
try
{
var verifyResponse =
await
(from acct in _twitterCtx.Account
where acct.Type == AccountType.VerifyCredentials
select acct)
.SingleOrDefaultAsync();
if (verifyResponse != null && verifyResponse.User != null)
{
User user = verifyResponse.User;
Console.WriteLine(
"Credentials are good for {0}.",
user.ScreenNameResponse);
}
}
catch (TwitterQueryException tqe)
{
Console.WriteLine(tqe.Message);
}
Of course, I checked the credentials several times, printed them out and all.
I tried with ApplicationOnlyAuthorizer, v.2, v.3 as well, it doesn't change anything.
What scares me the most is that what used to work (v2 + ApplicationOnly + Search API) doesn't work either.
Through my research I've heard of a problem caused by unsynchronized timestamps, or something like that. But I don't understand how I can change that.
The program is not on a server, it's locally stored.
Thank you for reading.
Here's how to use SingleUserAuthorizer in v3.0:
var auth = new SingleUserAuthorizer
{
CredentialStore = new SingleUserInMemoryCredentialStore
{
ConsumerKey = ConfigurationManager.AppSettings["consumerKey"],
ConsumerSecret = ConfigurationManager.AppSettings["consumerSecret"],
AccessToken = ConfigurationManager.AppSettings["accessToken"],
AccessTokenSecret = ConfigurationManager.AppSettings["accessTokenSecret"]
}
};
Notice here that I'm setting AccessToken and AccessToken secret. I also have a FAQ with suggestions for resolving 401 problems:
https://linqtotwitter.codeplex.com/wikipage?title=LINQ%20to%20Twitter%20FAQ&referringTitle=Documentation

LinkedIn OAUTH - Still can't get email-address using DotNetOpenAuth.AspNet

Having read that since late last year LinkedIn finally have finally allowed us to retrieve the email address for the currently logged on user I've been failing to do so. I've read all the posts I can find on SO and elsewhere and as far as I can tell my code should be working. It returns just fine with all the other fields,
however, the email address field is always empty.
Here's my LinkedInClient class;
public class LinkedInClient2 : OAuthClient
{
public static readonly ServiceProviderDescription LinkedInServiceDescription = new ServiceProviderDescription
{
AccessTokenEndpoint =
new MessageReceivingEndpoint(
"https://api.linkedin.com/uas/oauth/accessToken",
HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
RequestTokenEndpoint =
new MessageReceivingEndpoint(
"https://api.linkedin.com/uas/oauth/requestToken?scope=r_basicprofile+r_emailaddress",
HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
UserAuthorizationEndpoint =
new MessageReceivingEndpoint(
"https://www.linkedin.com/uas/oauth/authenticate",
HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() },
ProtocolVersion = ProtocolVersion.V10a
};
public LinkedInClient2(string consumerKey, string consumerSecret, IConsumerTokenManager tokenManager)
: base("linkedIn", LinkedInServiceDescription, tokenManager)
{
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We don't care if the request fails.")]
protected override AuthenticationResult VerifyAuthenticationCore(AuthorizedTokenResponse response)
{
// See here for Field Selectors API http://developer.linkedin.com/docs/DOC-1014
const string ProfileRequestUrl = "https://api.linkedin.com/v1/people/~:(id,first-name,last-name,email-address,headline,industry,summary,picture-url)";
string accessToken = response.AccessToken;
var profileEndpoint = new MessageReceivingEndpoint(ProfileRequestUrl, HttpDeliveryMethods.GetRequest);
HttpWebRequest request = this.WebWorker.PrepareAuthorizedRequest(profileEndpoint, accessToken);
try
{
using (WebResponse profileResponse = request.GetResponse())
{
using (Stream responseStream = profileResponse.GetResponseStream())
{
XDocument document = LoadXDocumentFromStream(responseStream);
string userId = document.Root.Element("id").Value;
// User Profile Fields - https://developer.linkedin.com/documents/profile-fields
string firstName = document.Root.Element("first-name").Value;
string lastName = document.Root.Element("last-name").Value;
string userName = document.Root.Element("email-address").Value; // <<<<<< ERROR - always empty
var extraData = new Dictionary<string, string>();
extraData.Add("accesstoken", accessToken);
extraData.Add("name", userName);
extraData.AddDataIfNotEmpty(document, "picture-url");
extraData.AddDataIfNotEmpty(document, "location");
extraData.AddDataIfNotEmpty(document, "headline");
extraData.AddDataIfNotEmpty(document, "summary");
extraData.AddDataIfNotEmpty(document, "industry");
return new AuthenticationResult(
isSuccessful: true, provider: this.ProviderName, providerUserId: userId, userName: userName, extraData: extraData);
}
}
}
catch (Exception exception)
{
return new AuthenticationResult(exception);
}
}
internal static XDocument LoadXDocumentFromStream(Stream stream)
{
const int MaxChars = 0x10000; // 64k
XmlReaderSettings settings = new XmlReaderSettings()
{
MaxCharactersInDocument = MaxChars
};
return XDocument.Load(XmlReader.Create(stream, settings));
}
}
}
I realise that I'm supposed to add the scope=r_emailaddress to the RequestTokenEndpoint (which I have) but from the fiddler traces I can't even see that endpoint being fetched. Basically, it only every uses the AccessTokenEndpoint which presumably is something to do with my problem.
This is approximately how my ASP.Net MVC4.5 controller looks;
[AllowAnonymous]
public virtual ActionResult LinkedIn(string returnUrl)
{
var tokenMgr = new RepoOAuthTokenManager(_iOtk, LinkedInAppKey, LinkedInAppSecret);
var iacp = new LinkedInClient2(LinkedInAppKey, LinkedInAppSecret, tokenMgr); // if none specified, LinkedInClient uses the AuthenticationOnlyCookieOAuthTokenManager which doesn't work for APIs
var ioadp = new MyOauthDataProvider();
var oasm = new OpenAuthSecurityManager(this.HttpContext, iacp, ioadp);
var redirectUri = Url.ActionFullyQualified(this.nameof(c => c.LinkedIn(null)), null, new RouteValueDictionary(new { returnUrl = returnUrl }));
AuthenticationResult ar = oasm.VerifyAuthentication(redirectUri);
if (ar.Error == null)
{
if (ar.IsSuccessful)
DoSomethingResultingInRedirect(redirectUri); // OK
else
oasm.RequestAuthentication(redirectUri);
}
else
ModelState.AddModelError("", ar.Error.Message);
return View(this.nameof(c=>c.Login(null)));
}//LinkedIn
I can't say I completely understand the extensibility mechanism in DotNetOpenAuth and I may be misunderstanding something so I'd appreciate some pointers.
Am I missing a step somewhere?
I have two solutions to this, although I still don't understand how to get my existing code to work as I'd expect, but hopefully this may help someone else;
(1) I went to Making it easier for you to add default member permissions and clicked on the API admin page.
Here you can select what scopes you want requested by default. It didn't work until I clicked a box (now disappeared) that was worded along the lines of "[x] Make this permanent". Once I'd done that I started to get the email-address field populated as I was expecting.
(2) I tried using the OAuth2 URL instead from information here and it seemed to work. I have also found an implementation of an OAuth2 client here which looks like a good start. I suspect that in the long run, an OAuth2 upgrade (once the spec is more static) will yield better overall mileage.
For now though, I'm out of the pit of despair, but other answers are still welcome!
I had a similar issue.. maybe this is relevant for you:
My Request Token Call is:
https://api.linkedin.com/v1/people/~:(id,first-name,last-name,headline,member-url-resources,picture-url,location,public-profile-url,email-address)?format=json
but the json response is:
array(8) {
["emailAddress"]=>
string(18) "email#email.com"
["firstName"]=>
string(3) "Tim"
...
Note that in the first case email is named email-address, in the second emailAddress.

Create SAML Authentication request using WIF

It seems most of the WIF information out there is useful for enabling federated authentication across entire applications. I'm interested in using the API to create SAML authentication requests and receive/interpret the SAML responses.
I found the following post on SO Reading SAML Attributes from SAML Token that gets me going in the right direction in regards to receiving and interpreting SAML responses. Can anyone give me more information on how I might use the API to create SAML requests?
Any more info (reading material, videos, etc) on the API in general would be greatly appreciated.
Here's a little example form one of our samples that shows how to programatically create a request for a (SAML) Security Token to an STS:
private static SecurityToken GetSamlToken(string realm, string stsEndpoint, ClientCredentials clientCredentials)
{
using (var factory = new WSTrustChannelFactory(
new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
new EndpointAddress(new Uri(stsEndpoint))))
{
factory.Credentials.UserName.UserName = clientCredentials.UserName.UserName;
factory.Credentials.UserName.Password = clientCredentials.UserName.Password;
factory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
factory.TrustVersion = TrustVersion.WSTrust13;
WSTrustChannel channel = null;
try
{
var rst = new RequestSecurityToken
{
RequestType = WSTrust13Constants.RequestTypes.Issue,
AppliesTo = new EndpointAddress(realm),
KeyType = KeyTypes.Bearer,
};
channel = (WSTrustChannel)factory.CreateChannel();
return channel.Issue(rst);
}
finally
{
if (channel != null)
{
channel.Abort();
}
factory.Abort();
}
}
Since no one else has answered, here's an article from the inimitable Michelle Bustamante:
http://www.devproconnections.com/article/federated-security/Generate-SAML-Tokens-Using-Windows-Identity-Foundation.aspx

Categories

Resources