読者です 読者をやめる 読者になる 読者になる

酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

はてなOAuthがちょっと使いにくいなと思った話

はてなOAuthがちょっと使いにくいなと思った話。

iOSではてなフォトライフに写真をアップロードするアプリを作りたいなと考えています。

f:id:ch3cooh393:20160625175246p:plain

はてなOAuthを使うのに最適な方法を考えているのですが思い付きませんでした。実際の最適解がよくわからないのでとりあえず実現だけさせてみました。

はてなOAuthのコールバックについて

サードパーティー製Twitterクライアントなど、よくあるOAuthを使うアプリの場合にはざっと下記のような順序です。

  1. アプリからSafariを起動する(あるいはSFSafariViewControllerを起動する)
  2. サービスにログインする
  3. コールバックでカスタムURLスキーマを叩いて元のアプリに戻ってくる
  4. アプリでログイン後の機能が使えるようになる

僕もこんな感じでログイン画面を作ろうと考えていました。

はてなOAuthも当然ですがコールバックを指定することができます。

2.2. で ユーザーがアクセスを許可した場合ははてなはユーザーを 2.1. の oauth_callback で指定されたURLに oauth_verifier パラメータを付けてリダイレクトします。また URL の代わりに "oob" が指定されていた場合は oauth_verifier の値をユーザーに表示しますので、ユーザーに入力を指示して下さい。

はてなOAuthのコールバックで指定できるものは下記の2つだとわかりました。

  • URL
  • oob

このURLが少しネックでphotouploader://hogeみたいなURLのコールバックを叩いてもらおうと思うもののエラーが発生してしまいました。悶々と悩んだ結果実際に使えるのは下記の2つだとわかりました。

  • http(https) から始まるURL
  • oob

はてなOAuthを作っているひと的には

  • コールバックにoobを指定して、Safariで認証してもらってアプリに戻ってアクセスコードを入力する
  • コールバックにhttp://適当なURL/を指定して、WebView画面で認証後にURLのドメイン部分を監視してフックする

のどちらかを想定しているのかと思いました。おそらく前者かな?

App Storeに並んでいるアプリで、はてなへのログインを促しているものは後者の実装が多いようです。Safariで認証させてからアプリを起動することはできないようです。

はてなのログイン画面を作ってみた

ここでは後者の方法ではてなのログイン画面を作ってみました。かなりざっくりとですがコードを抜粋しました。認証のためにOAuthSwiftを使いました。

static func login(parentViewController: UIViewController, consumerKey: String, consumerSecret: String) -> Future<Credential, NSError> {
    let promise = Promise<Credential, NSError>()
    
    let oauthswift = OAuth1Swift(
        consumerKey:    consumerKey,
        consumerSecret: consumerSecret,
        requestTokenUrl: "https://www.hatena.com/oauth/initiate",
        authorizeUrl:    "https://www.hatena.ne.jp/touch/oauth/authorize",
        accessTokenUrl:  "https://www.hatena.com/oauth/token"
    )
    
    let viewController = UIStoryboard.init(name: "LoginWebView", bundle: nil).instantiateInitialViewController() as! LoginWebViewController
    viewController.previousViewController = parentViewController
    
    oauthswift.authorize_url_handler = viewController
    oauthswift.authorizeWithCallbackURL(
        NSURL(string: "http://photouploader.example.jp/")!,
        success: { credential, response, parameters in
            let c = Credential()
            c.oauth_token = credential.oauth_token
            c.oauth_token_secret = credential.oauth_token_secret
            
            promise.success(c)
        },
        failure: { error in
            promise.failure(HatenaError())
        }
    )
    return promise.future
}

LoginWebViewController(と名付けたWebView画面)でログインしたあとにhttp://適当なURL/に遷移するのを見張るようにしました。

class LoginWebViewController: OAuthWebViewController {
    
    @IBOutlet weak var toolbar: Toolbar!
    @IBOutlet weak var webView: UIWebView!

    var targetURL : NSURL = NSURL()
    var previousViewController: UIViewController?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // もろもろ省略
        loadAddressURL()
    }

    // MARK:-
    
    override func doHandle(url: NSURL) {
        previousViewController?.navigationController?.pushViewController(self, animated: true)
    }
    
    // MARK:-
    
    override func handle(url: NSURL) {
        targetURL = url
        super.handle(url)
        
        loadAddressURL()
    }
}
extension LoginWebViewController: UIWebViewDelegate {
    func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
        print("\(request.URL)")
        if let url = request.URL where (url.host == "photouploader.example.jp"){
            OAuthSwift.handleOpenURL(url)
            self.dismissWebViewController()
            return false
        }
        return true
    }
}

これでログインするところまではいけると思います。

一番最初に書いた通りはてなOAuthを使ったログイン画面の最適解について悶々と考えた結果、アプリ本体に手をつける前に投げ出してしまいました。気が向いたらまた着手します。