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