401 for Request_Token process

oauth
ads

#1

I am trying to setup the authorization process for the twitter ads api. Whenever I send a request to the Request_Token endpoint, twitter responds with a 401 error. The error details returned is as follows:
"{“errors”:[{“code”:32,“message”:“Could not authenticate you.”}]}"

My VB.NET code is shown below. I’m sorry if it is a mess, I’ve been testing various ideas to solve this issue. Also, I modified it slightly before posting it. So pardon any bugs that would break the code. I would appreciate someone’s perspective on what I am doing wrong. I tried searching the community and on google to no avail.

#Region "Oauth Variables and Signature"
        Const oauth_version As String = "1.0"   'Should always be 1.0 for twitter
        Const oauth_signature_method As String = "HMAC-SHA1"

        ''' <summary>
        ''' Return the unix time in seconds for oauth_timestamp
        ''' </summary>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function GetUnixTime() As String
            Return Math.Floor((DateTime.UtcNow - New DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds).ToString
        End Function


        ''' <summary>
        ''' Generate 32 character string for oauth_nonce
        ''' </summary>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function GenerateNonce(Optional ByVal NonceLength As Int16 = 32) As String
            Dim random As New Random()
            Dim chars As String = "0123456789abcdef"
            Dim nonce As String = "" '= Convert.ToBase64String(New ASCIIEncoding().GetBytes(DateTime.Now.Ticks.ToString()))

            If Len(nonce) < NonceLength Then
                For i As Integer = 0 To NonceLength - 1
                    nonce += chars.Substring(Convert.ToInt32(Math.Floor(random.NextDouble() * chars.Length)), 1)
                Next
            ElseIf Len(nonce) > NonceLength Then
                nonce = Left(nonce, NonceLength)
            End If

            Return nonce
        End Function


        ''' <summary>
        ''' Generate an encrypted oAuth signature which Twitter will use to validate the request.
        ''' Used for oauth_signature
        ''' </summary>
        ''' <param name="endpoint">Messaging Receiving Endpoint</param>
        ''' <param name="oauth_nonce">Random 32 alphanumeric string twitter uses to determine if a request has been submitted multiple times</param>
        ''' <param name="oauth_timestamp">Seconds since Unix epoch; used to check when request was created. Old requests are rejected</param>
        ''' <param name="oauth_token">Access Token; user's permission share access to their account</param>
        ''' <param name="oauth_token_secret">Access Token Secret; corresponds to and created at the same time as the Access Token</param>
        ''' <param name="Method">GET, POST, DELETE, PUT, HEAD</param>
        ''' <param name="url_parameters">An array of type UrlParameter;  These can be parameters found in query string or form variables</param>
        ''' <returns>An encrypted oAuth signature</returns>
        ''' <remarks></remarks>
        Function GeneraOAuthSignature(
            ByVal endpoint As MessageReceivingEndpoint,
            ByVal oauth_nonce As String,
            ByVal oauth_timestamp As String,
            ByVal oauth_token As String,
            ByVal oauth_token_secret As String,
            Optional ByVal Method As String = "GET",
            Optional ByVal url_parameters As UrlParameter() = Nothing,
            Optional ByVal oauth_callback As String = Nothing) As String

            'Consumer Secret and Key
            Dim oauth_consumer_secret As String = ConfigurationManager.AppSettings("twitterConsumerSecret")
            Dim oauth_consumer_key As String = ConfigurationManager.AppSettings("twitterConsumerKey")

            'generate an encrypted oAuth signature which Twitter will use to validate the request.
            'To do this, all of the request data is concatenated into a particular format as follows
            Dim baseFormat = "{6}oauth_consumer_key={0}&oauth_nonce={1}&oauth_signature_method={2}" & _
                             "&oauth_timestamp={3}&{4}oauth_version={5}"

            'OAuth Variables
            Dim baseString As String = String.Format(baseFormat, _
                            oauth_consumer_key, _
                            oauth_nonce, _
                            oauth_signature_method, _
                            oauth_timestamp, _
                            If(Len(oauth_token) > 0, String.Format("oauth_token={0}&", oauth_token), ""), _
                            oauth_version,
                            If(Not oauth_callback Is Nothing, String.Format("oauth_callback={0}&", oauth_callback), ""))



            'URL Parameters
            If Not url_parameters Is Nothing Then
                For Each url_parameter As UrlParameter In url_parameters
                    baseString &= String.Format("&{0}={1}", url_parameter.parameter_name, url_parameter.escaped_data_value)
                Next
            End If

            baseString = String.Concat(Method & "&", Uri.EscapeDataString(endpoint.Location.AbsoluteUri), "&", Uri.EscapeDataString(baseString))

            'Using this base string, we then encrypt the data using a composite of the secret keys and the HMAC-SHA1 algorithm.
            Dim compositeKey As String = String.Concat(Uri.EscapeDataString(oauth_consumer_secret), "&", Uri.EscapeDataString(oauth_token_secret))
            Dim oauth_signature As String

            Dim hasher As HMACSHA1 = New HMACSHA1(ASCIIEncoding.ASCII.GetBytes(compositeKey))
            Using hasher
                oauth_signature = Convert.ToBase64String(hasher.ComputeHash(ASCIIEncoding.ASCII.GetBytes(baseString)))
            End Using

            Return oauth_signature
        End Function

        ''' <summary>
        ''' Generate a request to api service and return results
        ''' </summary>
        ''' <param name="endpoint">Message Receiving Endpoint</param>
        ''' <param name="oauth_token">Access Token; user's permission share access to their account</param>
        ''' <param name="oauth_token_secret">Access Token Secret; corresponds to and created at the same time as the Access Token</param>
        ''' <param name="Method">GET, POST, DELETE, PUT, HEAD</param>
        ''' <param name="url_parameters">An array of type UrlParameter;  These can be parameters found in query string or form variables</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function GenerateOAuthRequest(
            ByVal endpoint As MessageReceivingEndpoint,
            ByVal oauth_token As String,
            ByVal oauth_token_secret As String,
            Optional ByVal Method As String = "GET",
            Optional ByVal url_parameters As UrlParameter() = Nothing,
            Optional ByVal oauth_callback As String = Nothing)

            'Handle certification validation
            ServicePointManager.ServerCertificateValidationCallback = New System.Net.Security.RemoteCertificateValidationCallback(AddressOf AcceptAllCertifications)

            'Consumer Secret and Key
            Dim oauth_consumer_secret As String = ConfigurationManager.AppSettings("twitterConsumerSecret")
            Dim oauth_consumer_key As String = ConfigurationManager.AppSettings("twitterConsumerKey")

            'Unix time (seconds since Jan 1, 1970)
            Dim oauth_timestamp As String = GetUnixTime()

            'A 32 byte random alphanumeric string
            Dim oauth_nonce As String = GenerateNonce()

            'Generate an oauth signature
            Dim oauth_signature As String = GeneraOAuthSignature(
                    endpoint:=endpoint,
                    url_parameters:=url_parameters,
                    oauth_nonce:=oauth_nonce,
                    oauth_timestamp:=oauth_timestamp,
                    oauth_token:=oauth_token,
                    oauth_token_secret:=oauth_token_secret,
                    oauth_callback:=oauth_callback)

            'Setup Request Header
            'Dim headerFormat As String = "OAuth oauth_nonce=""{0}"", oauth_signature_method=""{1}"", oauth_timestamp=""{2}"", oauth_consumer_key=""{3}"", oauth_token=""{4}"", oauth_signature=""{5}"", oauth_version=""{6}"""
            Dim headerFormat As String = "OAuth {7}oauth_consumer_key=""{3}"", oauth_nonce=""{0}"", oauth_signature=""{5}"", oauth_signature_method=""{1}"", oauth_timestamp=""{2}"", oauth_token=""{4}"", oauth_version=""{6}"""

            'Authorization
            Dim authHeader As String = String.Format(headerFormat, Uri.EscapeDataString(oauth_nonce), Uri.EscapeDataString(oauth_signature_method), Uri.EscapeDataString(oauth_timestamp), Uri.EscapeDataString(oauth_consumer_key), _
                        Uri.EscapeDataString(oauth_token), Uri.EscapeDataString(oauth_signature), Uri.EscapeDataString(oauth_version), If(Not oauth_callback Is Nothing, String.Format("oauth_callback=""{0}"", ", oauth_callback), ""))

            'Set 100 Continue Behavior
            ServicePointManager.Expect100Continue = False

            'Create Web Request
            Dim req As HttpWebRequest
            Dim res As HttpWebResponse
            Dim streamReader As StreamReader
            Dim results As String = ""
            Dim encode As Encoding = System.Text.Encoding.GetEncoding("utf-8")

            'URL Parameters / Form Fields
            Dim queryString As String = ""
            Dim POSTdata As String = ""
            If Not url_parameters Is Nothing Then
                For Each url_parameter As UrlParameter In url_parameters
                    If Method.ToLower = "get" Then
                        queryString &= String.Format("{0}{1}={2}", If(Len(queryString) > 0, "&", "?"), url_parameter.parameter_name, url_parameter.escaped_data_value)
                    ElseIf Method.ToLower = "post" Then
                        POSTdata &= String.Format("{0}{1}={2}", If(Len(queryString) > 0, "&", ""), url_parameter.parameter_name, url_parameter.escaped_data_value)
                    End If
                Next
            End If

            'Create web request
            req = HttpWebRequest.Create(endpoint.Location.AbsoluteUri & If(Len(queryString) > 0, queryString, ""))
            req.Timeout = -1
            req.Headers.Add("Authorization", authHeader)
            req.Method = Method
            req.ContentType = "application/x-www-form-urlencoded"


            req.UseDefaultCredentials = True
            req.AuthenticationLevel = System.Net.Security.AuthenticationLevel.MutualAuthRequested
            req.PreAuthenticate = True

            req.UserAgent = "OAuth gem v0.4.4"
            req.ReadWriteTimeout = 600000
            req.Timeout = 600000

            'Post Data
            If Method.ToLower = "post" Then
                req.ContentType = "application/x-www-form-urlencoded"
                Dim encoding As New Text.UTF8Encoding() 'ASCIIEncoding
                Dim postByteArray() As Byte = encoding.GetBytes(POSTdata)
                req.ContentLength = postByteArray.Length
                Dim postStream As IO.Stream = req.GetRequestStream()
                postStream.Write(postByteArray, 0, postByteArray.Length)
                postStream.Close()
            End If


            'Read Response
            Try
                res = DirectCast(req.GetResponse(), HttpWebResponse)
                streamReader = New StreamReader(res.GetResponseStream(), encode)
                results = streamReader.ReadToEnd()
            Catch web_exception As WebException
                Try
                    If Not web_exception.Response Is Nothing Then
                        Dim rs As System.Net.WebResponse = web_exception.Response
                        Return (New StreamReader(rs.GetResponseStream)).ReadToEnd()
                    Else
                        results = String.Format("{""errors"":[{""code"":{0},""message"":""{1}""}]}", -1, web_exception.Message)
                    End If
                Catch ex As Exception
                    results = String.Format("{""errors"":[{""code"":{0},""message"":""{1}""}]}", -1, ex.Message)
                End Try
            End Try

            'Cleanup
            req.Abort()
            streamReader.Close()
            streamReader = Nothing
            res.Close()
            res = Nothing

            Return results
        End Function

        ''' <summary>
        ''' Handle ssl certification validation
        ''' </summary>
        ''' <param name="sender"></param>
        ''' <param name="certification"></param>
        ''' <param name="chain"></param>
        ''' <param name="sslPolicyErrors"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Private Function AcceptAllCertifications(ByVal sender As Object, ByVal certification As System.Security.Cryptography.X509Certificates.X509Certificate, ByVal chain As System.Security.Cryptography.X509Certificates.X509Chain, ByVal sslPolicyErrors As System.Net.Security.SslPolicyErrors) As Boolean
            Return True
        End Function
#End Region



#Region "Twitter Ads API - Authentication"

        ''' <summary>
        ''' Used for authenticating / authorizing
        ''' The description of Twitter's OAuth protocol URIs for use with actually reading/writing
        ''' a user's private Twitter data.
        ''' </summary>
        Public ReadOnly AdsAuthentication_ServiceDescription As ServiceProviderDescription = New ServiceProviderDescription With _
        {
            .RequestTokenEndpoint = New MessageReceivingEndpoint("https://api.twitter.com/oauth/request_token", HttpDeliveryMethods.PostRequest Or HttpDeliveryMethods.AuthorizationHeaderRequest),
            .UserAuthorizationEndpoint = New MessageReceivingEndpoint("https://api.twitter.com/oauth/authorize", HttpDeliveryMethods.GetRequest Or HttpDeliveryMethods.AuthorizationHeaderRequest),
            .AccessTokenEndpoint = New MessageReceivingEndpoint("https://api.twitter.com/oauth/access_token", HttpDeliveryMethods.PostRequest Or HttpDeliveryMethods.AuthorizationHeaderRequest),
            .TamperProtectionElements = New ITamperProtectionChannelBindingElement() {New HmacSha1SigningBindingElement()}
        }

        ''' <summary>
        ''' Gets the consumer to request a client to authorize this app to access their twitter ad account
        ''' </summary>
        Public Function AuthorizeAdsApi() As WebConsumer
            If (authorizeConsumer Is Nothing) Then
                SyncLock signInConsumerInitLock
                    authorizeConsumer = New WebConsumer(AdsAuthentication_ServiceDescription, AuthorizeSessionTokenManager)
                End SyncLock
            End If

            Return authorizeConsumer
        End Function

        ''' <summary>
        ''' A work around for the read only property mentioned above
        ''' </summary>
        ''' <remarks></remarks>
        Public Function AuthorizeSessionTokenManager() As InMemoryTokenManager
            If (ApiTokenManager Is Nothing) Then
                Dim consumerKey As String = ConfigurationManager.AppSettings("twitterConsumerKey")
                Dim consumerSecret As String = ConfigurationManager.AppSettings("twitterConsumerSecret")
                If (IsTwitterConsumerConfigured) Then
                    ApiTokenManager = New InMemoryTokenManager(consumerKey, consumerSecret)
                    ApiTokenManager.SiteID = SiteId
                    ApiTokenManager.Datasource = Datasource
                    ApiTokenManager.TwitterAccount = TwitterAccount
                Else
                    Throw New InvalidOperationException("No Twitter OAuth consumer key and secret could be found in web.config AppSettings.")
                End If
            End If

            Return ApiTokenManager
        End Function

        ''' <summary>
        ''' Prepares a redirect that will send the user to Twitter to sign in.
        ''' </summary>
        ''' <param name="forceNewLogin">if set to <c>true</c> the user will be required to re-enter their Twitter credentials even if already logged in to Twitter.</param>
        ''' <returns>The redirect message.</returns>
        ''' <remarks>
        ''' Call <see cref="OutgoingWebResponse.Send"/> or
        ''' <c>return StartSignInWithTwitter().<see cref="MessagingUtilities.AsActionResult">AsActionResult()</see></c>
        ''' to actually perform the redirect.
        ''' </remarks>
        Public Function StartAuthorization(ByVal forceNewLogin As Boolean) As String
            Dim redirectParameters = New Dictionary(Of String, String)
            If (forceNewLogin) Then
                redirectParameters("force_login") = "true"
            End If
            'Dim callback As Uri = MessagingUtilities.GetRequestUrlFromContext().StripQueryArgumentsWithPrefix("oauth_")
            Dim callbackURL As String = MessagingUtilities.GetRequestUrlFromContext().StripQueryArgumentsWithPrefix("oauth_").AbsoluteUri
            If Not callbackURL.ToLower.Contains("siteid=") Then callbackURL &= If(callbackURL.Contains("?"), "&", "?") & String.Format("SiteID={0}", SiteId)
            If Not callbackURL.ToLower.Contains("ddtwitteraccount=") Then callbackURL &= If(callbackURL.Contains("?"), "&", "?") & String.Format("ddTwitterAccount={0}", TwitterAccount)
            Dim callback As Uri = New Uri(callbackURL)

            
            return GenerateOAuthRequest(
                    endpoint:=AdsAuthentication_ServiceDescription.RequestTokenEndpoint,
                    oauth_token:="",
                    oauth_token_secret:="",
                    Method:="GET",
                    url_parameters:={New UrlParameter("force_login", If(forceNewLogin, "true", "false"))})

        End Function


        ''' <summary>
        ''' Checks the incoming web request to see if it carries a Twitter authentication response,
        ''' and provides the user's Twitter screen name and unique id if available.
        ''' </summary>
        ''' <param name="screenName">The user's Twitter screen name.</param>
        ''' <param name="userId">The user's Twitter unique user ID.</param>
        Public Sub TryFinishAuthorization(ByRef screenName As String, ByRef userId As String, ByRef AccessToken As String, ByRef AccessTokenSecret As String) 'As Boolean
            screenName = Nothing
            userId = 0
            Dim response = AuthorizeAdsApi.ProcessUserAuthorization()
            If (response Is Nothing) Then
                Exit Sub
                'Return False
            End If

            screenName = response.ExtraData("screen_name")
            userId = response.ExtraData("user_id")

            Try
                Dim consumerKey As String = ConfigurationManager.AppSettings("twitterConsumerKey")
                Dim consumerSecret As String = ConfigurationManager.AppSettings("twitterConsumerSecret")
                Dim tokenManager As New InMemoryTokenManager(consumerKey, consumerSecret)
                AccessToken = response.AccessToken
                AccessTokenSecret = tokenManager.GetAccessTokenSecretFromResponse(response)
                queries.SetAccessToken(
                    SiteID:=SiteId,
                    Datasource:=Datasource,
                    AccessToken:=AccessToken,
                    TwitterAccount:=TwitterAccount,
                    AccessTokenSecret:=AccessTokenSecret)
            Catch ex As Exception
                'Error quietly
            End Try


            ' If we were going to make this LOOK like OpenID even though it isn't,
            ' this seems like a reasonable, secure claimed id to allow the user to assume.
            Dim fake_claimed_id As OpenId.Identifier = String.Format(CultureInfo.InvariantCulture, "https://twitter.com/{0}#{1}", screenName, userId)
        End Sub
#End Region

#2

@MerkleDrone are you trying roll your own OAuth 1.0a implementation?

That’s a lot more complicated then it may at first seem and we really don’t recommend it. There are a number of tricky edge cases in character encoding and request signing that you’ll have to fight with and we recommend using a well-vetted existing library instead (in addition to really being outside of scope for these forums).

There are a few VB .NET libraries out there, but you should also be able to pull in any .NET library and use it in your code.

http://oauth.googlecode.com/svn/code/vbnet/oAuth.vb
http://dotnetopenauth.net/


#3

I looked at a couple libraries, but they seem oriented to making tweets and updating profile pictures rather than the ads api. I am still having no luck with this issue.


#4

Here’s an example using DotNetOpenAuth that could be fairly easily adapted to VB .NET and the advertiser API: