Tweedle supports all authentication methods that the Twitter v2 API supports (OAuth1, OAuth2 and OAuth2 PKCE)
The easiest authentication is using OAuth2 with a Bearer token found in your developer account. This token does have some limitations compared to others though, you can read about these limitations here
Example:
launch{
val oAuth2 = OAuth2(token)
val tweetLookup = TweetsLookup(oAuth2)
val response = tweetLookup.getTweet(tweetId)
}
An alternative to OAuth1
is OAuth2 PKCE
which allows for greater user control over access with scopes.
OAuth2 with PKCE usage is the same as using the Bearer token found in your developer account.
To read more about OAuth2 with PKCE see here
OAuth2 PKCE is recommended over using OAuth1 because OAuth1 currently will only perform GET operations
val oauth2 = OAuth2Bearer(bearerToken)
private val _tweetLookup:TweetsLookup = TweetsLookup(oauth2)
val response = _tweetLookup.getTweet(tweetId)
The main difference is how the token is created. An example of the OAuth2 PKCE flow is available in the sample app code but the majority of the code can be found in TwitterOAuth2Activity
Start the login flow by creating authentication url and passing it to the webview
//Scopes to request
val scopes = listOf(
OAuthScope.OfflineAccessScope,
OAuthScope.TweetReadScope,
OAuthScope.TweetWriteScope,
OAuthScope.TweetModerateWrite,
OAuthScope.UserBlockReadScope,
OAuthScope.UserBlockWriteScope,
OAuthScope.UserFollowsReadScope,
OAuthScope.UserFollowsWriteScope,
OAuthScope.UserMuteReadScope,
OAuthScope.UserMuteWriteScope,
OAuthScope.UserReadScope
)
//Client ID is found in your developer account
//Callback url is what you have in your developer account
//State is a random set of characters to verify against CSRF attacks. The length of this string can be up to 500 characters.
val allowedChars = ('A'..'Z') + ('a'..'z') + ('0'..'9')
val state = (1..10)
.map { allowedChars.random() }
.joinToString("")
//Challenge is a cryptographically random string using the characters A-Z, a-z, 0-9, and the punctuation characters -._~ (hyphen, period, underscore, and tilde), between 43 and 128 characters long.
val charPool = ('a'..'z') + ('A'..'Z') + ('0'..'9') + '-' + '.' + '_' + '~'
val challenge = (1..44)
.map { i -> kotlin.random.Random.nextInt(0, charPool.size) }
.map(charPool::get)
.joinToString("")
val url = Authentication2.generateAuthenticationUrl(clientId,scopes,callbackurl,state, challenge)
val webView: WebView = findViewById(R.id.webview)
val webSettings = webView.settings
webSettings.setJavaScriptEnabled(true)
webView.webViewClient = webViewClient
webView.loadUrl(url)
Create a WebViewClient and override shouldOverrideUrlLoading
to watch for then your callback url gets called so you can grab the
credentials if the user logs in successfully.
private val webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
if (request!!.url.toString().startsWith(callbackUrl)) {
return true
}
return false
}
}
When you see your callback url called you need to get the tokens from the url to save them
if (request!!.url.toString().startsWith(callbackUrl)){
val decodedUrl = URLDecoder.decode(request.url.toString(), "UTF-8")
if (decodedUrl.contains("code=")) {
val uri = Uri.parse(decodedUrl)
val code = uri.getQueryParameter("code")
val state = uri.getQueryParameter("state")
//Check to make sure the state perameter matches what you sent in the authorization url
if(state == savedState){
//States match you can proceed to the next step
}else{
//States don't match, do not proceed
}
}
}
With those the code and your challenge now you can get the access tokens by calling the request token api
val authentication2 = Authentication2(code, clientId)
val accessToken = authentication2.getAccessToken(callbackUrl, challenge)
To have a user login through their account and do things on their behalf you can start the login flow through the Authentication API
val _authenticationOauth = AuthenticationOAuth(apiKey, apiSecret)
val _authentication:Authentication = Authentication(authenticationOauth)
val token = _authentication.requestToken(callbackUrl)
an AccessTokenResponse
object is returned and you use that to load a login page in a webview for the user to login
val url = "https://api.twitter.com/oauth/authorize?oauth_token=${token.oauthToken}&force_login=true"
val webView: WebView = findViewById(R.id.webview)
webView.webViewClient = webViewClient
webView.loadUrl(url)
Create a WebViewClient and override shouldOverrideUrlLoading
to watch for then your callback url gets called so you can grab the
credentials if the user logs in successfully.
private val webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
if (request!!.url.toString().startsWith(callbackUrl)) {
return true
}
return false
}
}
When you see your callback url called you need to get the tokens from the url to save them
if (request!!.url.toString().startsWith(callbackUrl)){
val decodedUrl = URLDecoder.decode(request.url.toString(), "UTF-8")
if (decodedUrl.contains("oauth_token=")) {
val uri = Uri.parse(decodedUrl)
val authToken = uri.getQueryParameter("oauth_token")
val authVerifier = uri.getQueryParameter("oauth_verifier")
//Check to make sure oauth tokens match
if(oauthToken == authToken){
//Tokens match you can proceed to the next step
}else{
//Tokens don't match, do not proceed
}
}
}
With those tokens now you can get the access tokens by calling the request token api
val accessToken = _authentication.getAccessToken(oauthToken, oauthVerifier)
With the user access token you can now create an OAuth1
object to use to make API calls with on behalf of the user
val oauth1 = OAuth1(apiKey, apiSecret, accessToken.oauthToken!!, accessToken.oauthTokenSecret!!)
private val _tweetLookup:TweetsLookup = TweetsLookup(oauth1)
val response = _tweetLookup.getTweet(tweetId)
- Note OAuth1 currently only works with query calls such as getting tweets for example. API's that are not queries will throw an eception if you try to use them with OAuth1