Skip to content
This repository has been archived by the owner on Jun 30, 2023. It is now read-only.

Commit

Permalink
Merge pull request #138 from mironal/refresh-oauth20-helper
Browse files Browse the repository at this point in the history
Refresh oauth20 helper
  • Loading branch information
mironal committed Aug 3, 2022
2 parents 70a3c7d + b2b236e commit 4c6810d
Show file tree
Hide file tree
Showing 9 changed files with 558 additions and 81 deletions.
133 changes: 133 additions & 0 deletions HowDoIAuthenticate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# How do I authenticate?

There are multiple and complex ways to authenticate to Twitter. You will also need to configure your settings in the Developer Portal.

This document describes how to set up the Developer Portal and authenticate using TwitterAPIKit.

## Type of authentication method

- OAuth 1.0a (3-legged OAuth flow)
- PIN based
- OAuth 2.0 Authorization Code Flow with PKCE
- OAuth 2.0 Bearer Token (app-only)

## OAuth 1.0a (3-legged OAuth flow)

### Preparations

Turn on the OAuth 1.0a switch.

<div style="text-align: center;">
<img height="400" src="./imgs/AuthTypeSwitch.png" />
</div>

### Auth

> https://developer.twitter.com/en/docs/authentication/oauth-1-0a/obtaining-user-access-tokens
See the code in the sample project.

### PIN based

> https://developer.twitter.com/en/docs/authentication/oauth-1-0a/pin-based-oauth
```swift
// for CLI tool
func runOAuth() {
client.auth.postOAuthRequestToken(.init(oauthCallback: "oob")).responseObject(queue: .main) { response in
do {
let success = try response.result.get()
print("Token:", success)

let url = client.auth.makeOAuthAuthorizeURL(.init(oauthToken: success.oauthToken, forceLogin: true))!
print("Enter this URL into your browser and enter the PIN code that will be displayed after authentication.")
print(url)

let pinCode = readLine()!

client.auth.postOAuthAccessToken(.init(oauthToken: success.oauthToken, oauthVerifier: pinCode))
responseObject(queue: .main) { response in
do {
let success = try response.result.get()
print("AccessToken:", success)

} catch let error {
print("Error")
print(error)
}
}
} catch let error {
print("Error")
print(error)
}
}
}

// Output of runOAuth

/*
Token: TwitterOAuthTokenV1(oauthToken: "your-token", oauthTokenSecret: "your-secret", oauthCallbackConfirmed: Optional(true))
Enter this URL into your browser and enter the PIN code that will be displayed after authentication.
https://api.twitter.com/oauth/authorize?force_login=true&oauth_token=your-token
> your pin
AccessToken: TwitterOAuthAccessTokenV1(oauthToken: "", oauthTokenSecret: "", userID: Optional(""), screenName: Optional(""))
*/
```

## OAuth 2.0 Authorization Code Flow with PKCE

### Preparations

Turn on the OAuth 2.0 switch.

<div style="text-align: center;">
<img width="600" src="./imgs/AuthTypeSwitch.png" />
</div>

OAuth 2.0 Authorization Code Flow with PKCE has two authentication methods: Public Client and Confidential Client.

Please check which authentication method you have selected.

Most iOS applications are Public Client (Native App).

<div style="text-align: center;">
<img width="600" src="./imgs/OAuth20Type.png" />
</div>

### Auth

> https://developer.twitter.com/en/docs/authentication/oauth-2-0/authorization-code
See the code in the sample project.

## OAuth 2.0 Bearer Token (app-only)

> > https://developer.twitter.com/en/docs/authentication/oauth-2-0/application-only
```swift

var client: TwitterAPIClient!

func runAuth() {
client = TwitterAPIClient(
.basic(apiKey: "your consumer key", apiSecretKey: "your consumer secret")
)

client.auth.postOAuth2BearerToken(.init()).responseObject(queue: .main) { response in
do {
let success = try response.result.get()
print("Token:", success)
} catch let error {
print("Error")
print(error)
}
}
}

func useBearerToken() {
let client = TwitterAPIClient(.bearer("<token>"))
client.v1.getUserTimeline(.init(target: .screenName("twitterapi"))).responseData { response in
print(response.prettyString)
}
}
```
148 changes: 68 additions & 80 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,23 @@ Currently, scoping according to Twitter's App permissions is not yet implemented
```swift

// The most common usage.
let client = TwitterAPIClient(/* auth */)

// For OAuth 1.0a
let client = TwitterAPIClient(.oauth10a(.init(
consumerKey: "",
consumerSecret: "",
oauthToken: "",
oauthTokenSecret: ""
)))
// For OAuth 2.0 client
let client = TwitterAPIClient(.oauth20(.init(
clientID: "",
scope: [],
tokenType: "",
expiresIn: 0,
accessToken: "",
refreshToken: ""
)))

client.v1.someV1API()
client.v2.someV2API()
Expand All @@ -62,6 +78,14 @@ let client.v1.tweet.someTweetAPIs()
let client.v1.directMessage.someDM_APIs()
```

## How do I authenticate?

[Please see "HowDoIAuthenticate.md"](./HowDoIAuthenticate.md)

And the following sample project includes a sample authentication.

> https://github.com/mironal/TwitterAPIKit-iOS-sample
## How to decode response

[Please see "HowToDecodeResponse.md"](./HowToDecodeResponse.md)
Expand Down Expand Up @@ -122,6 +146,49 @@ This sample project contains examples of how to authenticate with `OAuth 1.0a Us
}
```

### Refresh OAuth 2.0 Token

```swift
let refresh = try await client.refreshOAuth20Token(type: .confidentialClient(clientID: "", clientSecret: ""), forceRefresh: true)
// let refresh = try await client.refreshOAuth20Token(type: .publicClient, forceRefresh: true)

// The authentication information in the Client is also updated, so there is no need to recreate a new instance of the Client.

if refresh.refreshed {
storeToken(refresh.token)
}

// Or

client.refreshOAuth20Token(type: .publicClient, forceRefresh: true) { result in
do {
let refresh = try result.get()
if refresh.refreshed {
storeToken(refresh.token)
}
} catch {

}
}

// Notification

NotificationCenter.default.addObserver(
self,
selector: #selector(didRefreshOAuth20Token(_:)),
name: TwitterAPIClient.didRefreshOAuth20Token,
object: nil
)

@objc func didRefreshOAuth20Token(_ notification: Notification) {
guard let token = notification.userInfo?[TwitterAPIClient.tokenUserInfoKey] as? TwitterAuthenticationMethod.OAuth20 else {
fatalError()
}
print("didRefreshOAuth20Token", didRefreshOAuth20Token, token)
store(token)
}
```

### Custom Request class

The class of each request can be inherited to create subclasses. This is why it is declared as an open class instead of a struct.
Expand Down Expand Up @@ -188,85 +255,6 @@ This method is intended to be used when the library does not yet support Twitter
}
```

### OAuth

#### PIN based

> https://developer.twitter.com/en/docs/authentication/oauth-1-0a/pin-based-oauth

```swift
// for CLI tool
func runOAuthV1() {
client.auth.postOAuthRequestToken(.init(oauthCallback: "oob")).responseObject(queue: .main) { response in
do {
let success = try response.result.get()
print("Token:", success)

let url = client.auth.makeOAuthAuthorizeURL(.init(oauthToken: success.oauthToken, forceLogin: true))!
print("Enter this URL into your browser and enter the PIN code that will be displayed after authentication.")
print(url)

let pinCode = readLine()!

client.auth.postOAuthAccessToken(.init(oauthToken: success.oauthToken, oauthVerifier: pinCode))
responseObject(queue: .main) { response in
do {
let success = try response.result.get()
print("AccessToken:", success)

} catch let error {
print("Error")
print(error)
}
}
} catch let error {
print("Error")
print(error)
}
}
}

// Output of runOAuthV1

/*
Token: TwitterOAuthTokenV1(oauthToken: "your-token", oauthTokenSecret: "your-secret", oauthCallbackConfirmed: Optional(true))
Enter this URL into your browser and enter the PIN code that will be displayed after authentication.
https://api.twitter.com/oauth/authorize?force_login=true&oauth_token=your-token
> your pin
AccessToken: TwitterOAuthAccessTokenV1(oauthToken: "", oauthTokenSecret: "", userID: Optional(""), screenName: Optional(""))
*/
```

### App-only authentication and OAuth 2.0 Bearer Token

> https://developer.twitter.com/en/docs/authentication/oauth-2-0/application-only

```swift
func runOAuth2V1() {
let client = TwitterAPIClient(
.basic(apiKey: "your consumer key", apiSecretKey: "your consumer secret")
)

client.auth.postOAuth2BearerToken(.init()).responseObject(queue: .main) { response in
do {
let success = try response.result.get()
print("Token:", success)
} catch let error {
print("Error")
print(error)
}
}
}

func useBearerTokenV1() {
let client = TwitterAPIClient(.bearer("AAAAAAAAAAAAAAAAAAAAA"))
client.v1.getUserTimeline(.init(target: .screenName("twitterapi"))).responseData { response in
print(response.prettyString)
}
}

```

### Swift Concurrency (experimental)

```swift
Expand Down
12 changes: 12 additions & 0 deletions Sources/TwitterAPIKit/Extensions/Concurrency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,16 @@ extension TwitterAPISessionStreamTask {
}
}


@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
extension TwitterAPIClient {
public func refreshOAuth20Token(type: TwitterAuthenticationMethod.OAuth20WithPKCEClientType, forceRefresh: Bool = false) async throws -> RefreshOAuth20TokenResultValue {
return try await withCheckedThrowingContinuation { c in
refreshOAuth20Token(type: type, forceRefresh: forceRefresh) { result in
c.resume(with: result)
}
}
}
}

#endif

0 comments on commit 4c6810d

Please sign in to comment.