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 の追加。
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) } ) } ・ ・ ・
実行すると、
- お試しアプリが起動
- (上記 ViewController に記載した OAuthSwift の処理で) Safari に自動遷移
- Safari 側でログイン処理を実施
- (Redirect URL に飛ぶ際 URL Schemes を使って)お試しアプリに自動遷移
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
を返却するよう実装する。(特に意図はないけどそれぐらいで良さそう?)
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" }, ・ ・ ・ ]
うん、意図通り。
レスポンス例が表示できて、
各属性の詳細が表示できて、
irb で AccessToken 発行まで進めておけば、Try it out! ボタンも正常に動作する。
良い感じである。
その他
これで下地が全部整った感あるので、
- REST API 全体設計
- 実装
と進めていく。
microposts とか、followers とか? 全容はまだぼんやり。
既存 Rails アプリに追加した REST API ドキュメンテーションにOAuth2 認可情報を反映する
既存 Rails アプリに追加した REST API ドキュメンテーションにOAuth2 認可情報を反映する
回を追うごとにタイトルが長くなっていくのはご愛嬌。
Rails チュートリアルで作成したアプリに
- REST API を追加
- OAuth2 で保護
- Swagger でドキュメンテーション
と進めてきた。
前回の Swagger ドキュメンテーションではセキュリティの定義をしていないので追加していく。
Swagger の SecurityDefinition
This page is in progress. Please check back later.
まぁオープンソースだしね、文句があるなら自分でパッチ送れや的な?
GrapeSwaggerRails の doorkeeper 連携
そもそも GrapeSwaggerRails が 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 とは
- インタラクティブなドキュメントの作成、閲覧
- ドキュメントからのスタブサーバーの自動生成
- ドキュメントからのクライアントコードの自動生成
ドキュメントを yml で記述していく方法と、実装済から生成する方法の2通りの作り方が可能。
今回は後者を試す。
準備
ありがたいことに Rails と連携できる便利 Gem を作っている方が 色々いらっしゃる ようで。
RSpec 前提のものが多い。(Rails チュートリアルは minitest で進んでたからこの辺はぐぬぬ
rswag とか強そうなんだけど。
domaindrivendev_rswag_ Seamlessly adds a Swagger to Rails-based API’s
今回選んだのはこれら。
- grape-swagger
- Grape で作成した REST API から、swagger 形式の JSON を生成してくれる
- GrapeSwaggerRails
- Rails アプリ上で Swagger UI が動かせる
この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
なんかページ数指定して続きを取る系が酷評されてるのを昔聞いた覚えが。