techium

このブログは何かに追われないと頑張れない人たちが週一更新をノルマに技術情報を発信するブログです。もし何か調査して欲しい内容がありましたら、@kobashinG or @muchiki0226 までいただけますと気が向いたら調査するかもしれません。

OAuthSwift で AccessToken 取得

OAuthSwift で Access Token 取得

Doorkeeper - awesome OAuth2 provider for your Rails app. を使った OAuth2 認可を実装したサーバーに対して iOS アプリから実際にアクセストークンの取得を試みる。

OSS でいくつか公開されているライブラリがあるが、GitHub スター数や、更新の活発さ等から以下を選択、使用してみたところ正常にアクセストークンを取得できた。

OAuthSwift/OAuthSwift/ Swift based OAuth library for iOS

Doorkeeper 側の設定

https://YOUR_DOMAIN/oauth/applications にアクセスし、対象のアプリケーションを選択する。

Callback urls にはこれまで local tests 用の urn:ietf:wg:oauth:2.0:oob しか入れていなかったので Edit 押下で Redirect URI に追記。

urn:ietf:wg:oauth:2.0:oob
sample-app://oauth-callback

なんか命名規則っぽいのはあるのかな。とりあえず付けた感。

Installation

iOS アプリ側に戻る。
今回パッケージ管理には Carthage を使用しているので Support Carthage の通りに。

How to

How to の通りに。

URL Schemes の追加。

f:id:kfurue:20170830074213p:plain

AppDelegate.swift に以下を追記。

import UIKit
import OAuthSwift

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    
    func application(_ app: UIApplication,
                     open url: URL,
                     options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
        if (url.host == "oauth-callback") {
            OAuthSwift.handle(url: url)
        }
        return true
    }
・
・
・

とりあえず動作確認用なので、ViewController 上でお試し。

ViewController.swift に以下を追記。

import UIKit
import OAuthSwift

class ViewController: UIViewController {

    let oauthswift = OAuth2Swift(
        consumerKey:    "15afda6f72009b78571150d63f57bc690a436c8e1199c09bc2de05bc63c09f14",
        consumerSecret: "e590044244da414ece872de0ada52aed33d1cf889d365cb3da4dceed710668b8",
        authorizeUrl:   "https://fierce-wave-40771.herokuapp.com/oauth/authorize",
        accessTokenUrl: "https://fierce-wave-40771.herokuapp.com/oauth/token",
        responseType:   "code"
    )
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        let handle = oauthswift.authorize(
            withCallbackURL: URL(string: "sample-app://oauth-callback")!,
            scope: "", state:"hoge",
            success: { credential, response, parameters in
                print(credential.oauthToken)
                // Do your request
        },
            failure: { error in
                print(error.localizedDescription)
        }
        )

    }
・
・
・

実行すると、

  1. お試しアプリが起動
  2. (上記 ViewController に記載した OAuthSwift の処理で) Safari に自動遷移
  3. Safari 側でログイン処理を実施
  4. (Redirect URL に飛ぶ際 URL Schemes を使って)お試しアプリに自動遷移
  5. print(credential.oauthToken) の行でアクセストークンが取得できていることが確認できる

となる。

成功だ。

その他

テストについてちょっと考えていたが、このあたり Facebook はよくできているなと感じた。

テストユーザー - アプリ開発

まー Web API へのリクエストからしてそもそもモックにすることで unit test は可能か。

REST API 実装

REST API 実装

Rails チュートリアルで作成したアプリに REST API を実装するシリーズ。

  • Doorkeeper による OAuth2 認可
  • Grape による REST API 実装
  • Swagger(OpneAPI) を用いたドキュメンテーションの追加
  • Grape Entities を使ったドキュメント中のレスポンス定義

と進めてきたので、前回決めた API 設計に沿って実装していく。

続きを読む

REST API 設計

REST API 設計

Rails チュートリアルで作成したアプリに REST API を実装するシリーズ。

  • Doorkeeper による OAuth2 認可
  • Grape による REST API 実装
  • Swagger(OpneAPI) を用いたドキュメンテーションの追加
  • Grape Entities を使ったドキュメント中のレスポンス定義

と必要な要素は全て揃った感があるので、REST API の全容を決めていく。

続きを読む

Using Grape Entities

Using Grape Entities

Rails チュートリアルで作成したアプリに REST API を実装するシリーズ。

  • Doorkeeper による OAuth2 認可
  • Grape による REST API 実装
  • Swagger(OpneAPI) を用いたドキュメンテーションの追加

と進めてきて、今回は Grape Entities を使ってドキュメント中のレスポンス定義を明記してみる。

インストール

Using Grape Entities に沿って進める。

Grape::EntityインストールGrapeSwagger::Entityインストール

gem 'grape-entity'
gem 'grape-swagger-entity'
$ bundle

実装

  • Grape::Entity で、レスポンスを整形
  • GrapeSwagger::Entity で、ドキュメントの記載を整形

基本的にこうなる。

User API を実際に書き換えてみる。
今まではとりあえず以下のように User モデルの内容をそのまま返却していた。

module V1

  class Users < Grape::API
・
・
・
    resource :users do

      desc 'Return all users.'
      get do
        User.all
      end
・
・
・

実際のレスポンスはこんな感じ。

[
  {
    "id": 1,
    "name": "Example User",
    "email": "example@railstutorial.org",
    "created_at": "2017-07-22T02:26:19.028Z",
    "updated_at": "2017-07-22T02:26:19.028Z",
    "password_digest": "hogehoge",
    "remember_digest": null,
    "admin": true,
    "activation_digest": "hogehoge",
    "activated": true,
    "activated_at": "2017-07-22T02:26:18.922Z",
    "reset_digest": null,
    "reset_sent_at": null
  },
・
・
・
]

もはや何でもありで User データモデルの内容を全て返却している状態。
これを、

  • 自分が意図した属性のみ返却
  • 返却される値の定義をドキュメンテーション

とするのが今回の目標。

まずは Entity の定義から。

今回は全属性のうち、

  • id
  • name
  • email

を返却するよう実装する。(特に意図はないけどそれぐらいで良さそう?)
created_at も入れておけば「このユーザーはいつから利用を開始しているか」とか表示できてそれっぽいか。
まぁ必要になったら追加することにしよう。

app/api/v1/users.rb に以下を追記。

module V1
  module Entities
    class User < Grape::Entity
      expose :id, documentation: { type: 'integer', desc: 'User ID.', required: true }
      expose :name, documentation: { type: 'string', desc: 'User name.', required: true }
      expose :email, documentation: { type: 'string', desc: 'User email address', required: true }
    end

  end

  class Users < Grape::API
・
・
・

とりあえず型と詳細、optional かどうかの指定をセットしている。
(他にも設定できる内容はたくさんある。)

次に実際に処理を書き換える。

module V1

  class Users < Grape::API
・
・
・
    resource :users do

      desc 'Return all users.',
        entity: Entities::User
      get do
        user = User.all
        present user, with: Entities::User
      end
・
・
・

これだけ。
動かしてみる。

[
  {
    "id": 1,
    "name": "Example User",
    "email": "example@railstutorial.org"
  },
・
・
・
]

うん、意図通り。

レスポンス例が表示できて、

f:id:kfurue:20170815112736p:plain

各属性の詳細が表示できて、

f:id:kfurue:20170815112751p:plain

irb で AccessToken 発行まで進めておけば、Try it out! ボタンも正常に動作する。

f:id:kfurue:20170815112801p:plain

良い感じである。

その他

これで下地が全部整った感あるので、

  • REST API 全体設計
  • 実装

と進めていく。

microposts とか、followers とか? 全容はまだぼんやり。

既存 Rails アプリに追加した REST API ドキュメンテーションにOAuth2 認可情報を反映する

既存 Rails アプリに追加した REST API ドキュメンテーションにOAuth2 認可情報を反映する

回を追うごとにタイトルが長くなっていくのはご愛嬌。

Rails チュートリアルで作成したアプリに
- REST API を追加 - OAuth2 で保護 - Swagger でドキュメンテーション

と進めてきた。

前回の Swagger ドキュメンテーションではセキュリティの定義をしていないので追加していく。

Swagger の SecurityDefinition

OAuth 2

This page is in progress. Please check back later.

まぁオープンソースだしね、文句があるなら自分でパッチ送れや的な?

GrapeSwaggerRails の doorkeeper 連携

そもそも GrapeSwaggerRails が doorkeeper と連携できるんでは?と思って見てみたらやはり。

Integration with DoorKeeper

user.rb は上記例の通り。

has_one :token, -> { order 'created_at DESC' }, class_name: Doorkeeper::AccessToken, foreign_key: :resource_owner_id

swagger.rb は、以下のようにある。

GrapeSwaggerRails.options.before_action do |request|
  GrapeSwaggerRails.options.api_key_default_value = current_user.token.token
end

最終的な GrapeSwaggerRails.options.before_action は以下のようになった。

GrapeSwaggerRails.options.before_action do |request|
  GrapeSwaggerRails.options.app_url = request.protocol + request.host_with_port

  if (user_id = session[:user_id])
    current_user ||= User.find_by(id: user_id)
    if (current_user_token = current_user.token)
      GrapeSwaggerRails.options.api_key_default_value = current_user_token.token
    end
  else
    session[:forwarding_url] = request.fullpath
    redirect_to(GrapeSwaggerRails.options.app_url + '/login')
  end
end

さらに こちら を参考に、api_key の値をクエリパラメータでは無くヘッダーで渡すよう設定する。

GrapeSwaggerRails.options.api_auth     = 'bearer'
GrapeSwaggerRails.options.api_key_name = 'Authorization'
GrapeSwaggerRails.options.api_key_type = 'header'

これで、irb から AccessToken 取得まで進めておけば、そのユーザーでログインした(セッションが有効な)状態で https://host:port/swagger にアクセスすればアクセストークンを api_key として持った状態で Swagger UI が表示できる。
各 API の Try it out! ボタンも正常に動作する。

その他

irb 使わなくても、Swagger UI から認可のフローが実行できないものか。。?
ちょっと だいぶ調べて、できそうなんだけどうまくいかないので断念。

api/v1/root.rb に以下を追加したところ、Swagger の json には SecurityDefinition の定義もできているが、Authorize 用の UI が表示されない。

    add_swagger_documentation \
    info: {
      title: "SAMPLE APP",
      description: "This is the sample application for the tutorial.",
      contact_name: "kfurue",
      contact_email: "contact@example.com",
      contact_url: "http://example.com/contact",
      license: "the MIT License",
      license_url: "http://example.com/license"
    },
    security_definitions: {
      oauthAccessCode: {
        type: "oauth2",
        authorizationUrl: "https://host:port/oauth/authorize",
        tokenUrl: 'https://host:port/oauth/token',
        flow: "accessCode",
        scopes: {
          user: "User scope"
        }
      },
      oauthImplicit: {
        type: "oauth2",
        authorizationUrl: "https://host:port/oauth/authorize",
        flow: "implicit",
        scopes: {
          user: "User scope"
        }
      }
    }

GrapeSwaggerRails のせいか?と思い、grape-swagger で生成した JSON を元に、ローカルで Swagger UI を立ち上げてみるとなるほど、Authorization 用の UI は出てくる。 が、今度は

Validation Erroroauth2RedirectUri configuration is not passed. Oauth2 authorization cannot be performed.

などと表示されて認可フローは通せず。

まだまだ壁は多そうなのでここらで諦めよう。
やりたいことはできてるっぽいし?

既存 Rails アプリに REST API ドキュメンテーションを追加する

既存 Rails アプリに REST API ドキュメンテーションを追加する

Rails チュートリアルで作成したアプリに REST API を実装するシリーズ。

  • Doorkeeper による OAuth2 認可
  • Grape による REST API 実装

と進めてきて、今回は Swagger(OpneAPI) を用いたドキュメンテーションを追加してみる。

OpenAPI とは

Open API Initiative

  • インタラクティブなドキュメントの作成、閲覧
  • ドキュメントからのスタブサーバーの自動生成
  • ドキュメントからのクライアントコードの自動生成

ドキュメントを yml で記述していく方法と、実装済から生成する方法の2通りの作り方が可能。

今回は後者を試す。

準備

ありがたいことに Rails と連携できる便利 Gem を作っている方が 色々いらっしゃる ようで。
RSpec 前提のものが多い。(Rails チュートリアルは minitest で進んでたからこの辺はぐぬぬ

rswag とか強そうなんだけど。
domaindrivendev_rswag_ Seamlessly adds a Swagger to Rails-based API’s

今回選んだのはこれら。

この2つで最低限動かせるはず。
grape-swagger-entity を組み合わせてより何たらかんたらできそう。

grape-swagger

Gemfile に以下を追記する。

gem 'grape-swagger'

以下を実行。

$ bundle install

Usage に従う。

Mount all your different APIs (with Grape::API superclass) on a root node. In the root class definition, include add_swagger_documentation, this sets up the system and registers the documentation on ‘/swagger_doc’.

Oh…
前回 試行錯誤の上見出した実装方法は上記にそぐわないのでやり直し。

再度試行錯誤の結果以下のように落ち着いた。

$ tree app/api/
app/api/
├── api.rb
├── v1
│   ├── root.rb
│   └── users.rb
└── v2
    ├── root.rb
    └── users.rb

Grape と Rails の組み合わせではディレクトリ名とモジュール名を揃える、というのを念頭に。

ルーティングから設定し直し。

config/routes.rb

Rails.application.routes.draw do
・
・
・
  mount API::Root => '/'
end

app/api/api.rb

module API
  class Root < Grape::API
    format :json
    mount V1::Root
    mount V2::Root
  end
end

app/api/v1/root.rb

require 'grape-swagger'

module V1
  class Root < Grape::API
    format :json
    mount V1::Users
    add_swagger_documentation
  end
end

$ rails s して https://[YOUR_APP_URL]/api/v1/swagger_doc にアクセスすると、Swagger 用の JSON が返却される。

{
  "info": {
    "title": "API title",
    "version": "0.0.1"
  },
  "swagger": "2.0",
  "produces": [
    "application/json"
  ],
  "host": "fierce-wave-40771.herokuapp.com",
  "tags": [
    {
      "name": "users",
      "description": "Operations about users"
    }
  ],
  "paths": {
    "/api/v1/users": {
      "get": {
        "summary": "Return all users.",
        "description": "Return all users.",
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Return all users."
          }
        },
        "tags": [
          "users"
        ],
        "operationId": "getApiV1Users"
      }
    },
    "/api/v1/users/{id}": {
      "get": {
        "summary": "Return a user.",
        "description": "Return a user.",
        "produces": [
          "application/json"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "description": "User id.",
            "type": "integer",
            "format": "int32",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Return a user."
          }
        },
        "tags": [
          "users"
        ],
        "operationId": "getApiV1UsersId"
      }
    }
  }
}

パスを v2 に変更すればもちろん v2 も返却される。

GrapeSwaggerRails

Swagger 用の JSON が生成できるようになったので、 これを Swagger UI で見れるようにする。

Gemfile に以下を追記する。

gem 'grape-swagger-rails'

以下を実行。

$ bundle install

以降、Usage に従う。

config/routes.rb を修正してルーティングを追加する。

Rails.application.routes.draw do
・
・
・
  mount API::Root => '/'
  mount GrapeSwaggerRails::Engine => '/swagger'
end

config/initializers/swagger.rb を作成し、以下を記載する。

GrapeSwaggerRails.options.url      = '/api/v1/swagger_doc'
GrapeSwaggerRails.options.app_url  = 'https://[YOUR_APP_URL]'

上記は先ほど grape-swagger で作成した JSON ファイルを指すようにする。

$ rails s して https://[YOUR_APP_URL]/swagger にアクセスすると、Swagger UI が表示される。

これだと開発環境と本番環境で共用できないので動的に URL を変更する。
上記の2行目を以下で置き換える。

GrapeSwaggerRails.options.before_action do |request|
  GrapeSwaggerRails.options.app_url = request.protocol + request.host_with_port
end

同じく $ rails s して https://[YOUR_APP_URL]/swagger にアクセスすると、Swagger UI が表示される。

その他

引き続き、

  • OAuth2 認可情報のドキュメンテーションへの反映
  • grape-swagger-entity を使ってレスポンスを整形

と進めていく。

そうこうしているうちに OpenAPI Specification の v3 が公開されてしまった。 The OAI Announces the OpenAPI Specification 3.0.0 – Open API Initiative

変更点について調べてみよっと。

REST API のページネーションについてはこの辺参考にすれば良いのだろうか。
Pagination in the REST API - Atlassian Developers

なんかページ数指定して続きを取る系が酷評されてるのを昔聞いた覚えが。