APIとモバイルの構成(モバイルアプリ + API)
このセクションでは、当社シナリオでAPIを実装する方法を説明します。
APIエンドポイントの定義
まず、APIのエンドポイントを定義する必要があります。
APIエンドポイントとは
APIエンドポイントは
たとえば、レストランAPIには/orders
や/customers
などのエンドポイントがあるかもしれません。このAPIに接続するアプリケーションは、関連するHTTPメソッド(POST
、GET
、PUT
、PATCH
、DELETE
)を使ってAPIエンドポイントを呼び出すことにより、CRUD操作(作成、読み取り、更新、削除)を実行することができます。
この実装では、2つのエンドポイントのみ定義します。1つは従業員の全タイムシートのリストを取得するため、もう1つは従業員がタイムシートエントリーを新規作成できるようにするためのものです。
/timesheets
エンドポイントへのHTTP GET
要求は、ユーザーがタイムシートを取得できるようにし、/timesheets
へのHTTP POST
要求は、ユーザーが新たなタイムシートを追加できるようにします。
エンドポイントのセキュリティ確保
APIがヘッダーの一部にBearerアクセストークンのある要求を受け取った場合、最初にすべきことはトークンの検証です。これは複数の手順で構成され、そのうち1つでも失敗した場合、要求は、Missing or invalid token
という呼び出し元アプリへのエラーメッセージとともに拒否されなければなりません。
APIは以下の検証を実行すべきです。
JWTが整形式であることを確認する
署名を確認する
標準クレームを検証する
検証プロセスにはクライアント権限(スコープ)の確認も含まれますが、これに関しては次の段落で別に説明します。
アクセストークン検証の詳細については、「アクセストークンを検証する」を参照してください。
クライアントの権限の確認
この段階ではJWTの有効性が検証されています。最後の手順は、クライアントが保護されたリソースにアクセスするために必要な権限を持っているかどうかを検証することです。
そのためには、APIがデコードされたJWTのスコープを確認する必要があります。このクレームはペイロードの一部で、スペースで区切られた文字列のリストです。
ユーザーIDの判断
どちらのエンドポイント(タイムシートのリスト取得用と新規タイムシート追加用)でも、ユーザーのIDを判断する必要があります。
これは、タイムシートのリスト取得に関しては、要求元のユーザーのタイムシートのみを返すようにするためです。一方の新規タイムシートの追加に関しては、タイムシートがその要求元のユーザーと関連付けられていることを確認するためです。
標準JWTクレームの1つは、クレームの対象である本人を識別するsub
クレームです。暗黙的付与フローの場合、このクレームにはAuth0ユーザーの一意の識別子であるIDが含まれます。これを使って、外部システムのいかなる情報でも、特定ユーザーに関連付けることができます。
また、カスタムクレームを使って、メールアドレスなど別のユーザー属性をアクセストークンに追加し、ユーザーを一意に識別することもできます。
モバイルアプリの実装
このセクションでは、当社シナリオでモバイルアプリケーションを実装する方法を説明します。
ユーザーの認可
ユーザーを認可するには、Proof Key for Code Exchange(PKCE)での認可コードフローを実装します。モバイルアプリケーションは最初に、code_challenge
および生成するために使用するメソッドと一緒に、ユーザーを認可URLに送信する必要があります。
https://{yourDomain}/authorize?
audience=API_AUDIENCE&
scope=SCOPE&
response_type=code&
client_id=YOUR_CLIENT_ID&
code_challenge=CODE_CHALLENGE&
code_challenge_method=S256&
redirect_uri=https://YOUR_APP/callback
Was this helpful?
認可URLへのGET
要求は、以下の値を含める必要があります。
パラメーター | 説明 |
---|---|
client_id | Auth0クライアントIDの値です。Auth0 Dashboardにあるアプリケーションの設定から取得できます。 |
audience | API識別子の値です。Auth0 DashboardにあるAPIの設定から取得できます。 |
scope | IDトークンとアクセストークンで返されるクレームを決定するスコープ です。たとえば、openid スコープではIDトークンが返されます。提供しているモバイルアプリの例では、次のスコープを使用しています:create:timesheets read:timesheets openid profile email offline_access 。これらのスコープによって、モバイルアプリはAPIを呼び出し、リフレッシュトークンを取得して、IDトークンでユーザーのname 、picture 、email クレームを返すことができます。 |
response_type | 使用する認証フローです。PKCEを使用するモバイルアプリケーションの場合は、これをcode に設定します。 |
code_challenge | コード検証が生成したコードチャレンジです。コードチャレンジの生成方法については、こちらを参照してください。 |
code_challenge_method | チャレンジの生成方法です。Auth0はS256 のみに対応しています。 |
redirect_uri | ユーザーが認可された後に、Auth0がブラウザーをダイレクトするURLです。認可コードはURLパラメーターのcodeにあります。このURLは、有効なコールバックURLとしてアプリケーションの設定で指定されなければなりません。 |
資格情報の取得
認可URLへの要求が成功したら、以下の応答を受け取ります。
HTTP/1.1 302 Found
Location: https://{yourDomain}/callback?code=AUTHORIZATION_CODE
Was this helpful?
次に、応答の認可コード
をAPIを呼び出すのに使用するアクセストークンと交換します。以下のデータを含めて、トークン URLへのPOST
要求を実行します。
curl --request POST \
--url 'https://{yourDomain}/oauth/token' \
--header 'content-type: application/x-www-form-urlencoded' \
--data grant_type=authorization_code \
--data 'client_id={yourClientId}' \
--data code_verified=YOUR_GENERATED_CODE_VERIFIER \
--data code=YOUR_AUTHORIZATION_CODE \
--data 'redirect_uri=https://{https://yourApp/callback}'
Was this helpful?
var client = new RestClient("https://{yourDomain}/oauth/token");
var request = new RestRequest(Method.POST);
request.AddHeader("content-type", "application/x-www-form-urlencoded");
request.AddParameter("application/x-www-form-urlencoded", "grant_type=authorization_code&client_id={yourClientId}&code_verified=YOUR_GENERATED_CODE_VERIFIER&code=YOUR_AUTHORIZATION_CODE&redirect_uri=https%3A%2F%2F{https://yourApp/callback}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
Was this helpful?
package main
import (
"fmt"
"strings"
"net/http"
"io/ioutil"
)
func main() {
url := "https://{yourDomain}/oauth/token"
payload := strings.NewReader("grant_type=authorization_code&client_id={yourClientId}&code_verified=YOUR_GENERATED_CODE_VERIFIER&code=YOUR_AUTHORIZATION_CODE&redirect_uri=https%3A%2F%2F{https://yourApp/callback}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("content-type", "application/x-www-form-urlencoded")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := ioutil.ReadAll(res.Body)
fmt.Println(res)
fmt.Println(string(body))
}
Was this helpful?
HttpResponse<String> response = Unirest.post("https://{yourDomain}/oauth/token")
.header("content-type", "application/x-www-form-urlencoded")
.body("grant_type=authorization_code&client_id={yourClientId}&code_verified=YOUR_GENERATED_CODE_VERIFIER&code=YOUR_AUTHORIZATION_CODE&redirect_uri=https%3A%2F%2F{https://yourApp/callback}")
.asString();
Was this helpful?
var axios = require("axios").default;
var options = {
method: 'POST',
url: 'https://{yourDomain}/oauth/token',
headers: {'content-type': 'application/x-www-form-urlencoded'},
data: new URLSearchParams({
grant_type: 'authorization_code',
client_id: '{yourClientId}',
code_verified: 'YOUR_GENERATED_CODE_VERIFIER',
code: 'YOUR_AUTHORIZATION_CODE',
redirect_uri: 'https://{https://yourApp/callback}'
})
};
axios.request(options).then(function (response) {
console.log(response.data);
}).catch(function (error) {
console.error(error);
});
Was this helpful?
#import <Foundation/Foundation.h>
NSDictionary *headers = @{ @"content-type": @"application/x-www-form-urlencoded" };
NSMutableData *postData = [[NSMutableData alloc] initWithData:[@"grant_type=authorization_code" dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:[@"&client_id={yourClientId}" dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:[@"&code_verified=YOUR_GENERATED_CODE_VERIFIER" dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:[@"&code=YOUR_AUTHORIZATION_CODE" dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:[@"&redirect_uri=https://{https://yourApp/callback}" dataUsingEncoding:NSUTF8StringEncoding]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://{yourDomain}/oauth/token"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:10.0];
[request setHTTPMethod:@"POST"];
[request setAllHTTPHeaderFields:headers];
[request setHTTPBody:postData];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"%@", error);
} else {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
NSLog(@"%@", httpResponse);
}
}];
[dataTask resume];
Was this helpful?
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://{yourDomain}/oauth/token",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "grant_type=authorization_code&client_id={yourClientId}&code_verified=YOUR_GENERATED_CODE_VERIFIER&code=YOUR_AUTHORIZATION_CODE&redirect_uri=https%3A%2F%2F{https://yourApp/callback}",
CURLOPT_HTTPHEADER => [
"content-type: application/x-www-form-urlencoded"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}
Was this helpful?
import http.client
conn = http.client.HTTPSConnection("")
payload = "grant_type=authorization_code&client_id={yourClientId}&code_verified=YOUR_GENERATED_CODE_VERIFIER&code=YOUR_AUTHORIZATION_CODE&redirect_uri=https%3A%2F%2F{https://yourApp/callback}"
headers = { 'content-type': "application/x-www-form-urlencoded" }
conn.request("POST", "/{yourDomain}/oauth/token", payload, headers)
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))
Was this helpful?
require 'uri'
require 'net/http'
require 'openssl'
url = URI("https://{yourDomain}/oauth/token")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Post.new(url)
request["content-type"] = 'application/x-www-form-urlencoded'
request.body = "grant_type=authorization_code&client_id={yourClientId}&code_verified=YOUR_GENERATED_CODE_VERIFIER&code=YOUR_AUTHORIZATION_CODE&redirect_uri=https%3A%2F%2F{https://yourApp/callback}"
response = http.request(request)
puts response.read_body
Was this helpful?
import Foundation
let headers = ["content-type": "application/x-www-form-urlencoded"]
let postData = NSMutableData(data: "grant_type=authorization_code".data(using: String.Encoding.utf8)!)
postData.append("&client_id={yourClientId}".data(using: String.Encoding.utf8)!)
postData.append("&code_verified=YOUR_GENERATED_CODE_VERIFIER".data(using: String.Encoding.utf8)!)
postData.append("&code=YOUR_AUTHORIZATION_CODE".data(using: String.Encoding.utf8)!)
postData.append("&redirect_uri=https://{https://yourApp/callback}".data(using: String.Encoding.utf8)!)
let request = NSMutableURLRequest(url: NSURL(string: "https://{yourDomain}/oauth/token")! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error)
} else {
let httpResponse = response as? HTTPURLResponse
print(httpResponse)
}
})
dataTask.resume()
Was this helpful?
パラメーター | 説明 |
---|---|
grant_type | authorization_code に設定する必要があります。 |
client_id | Auth0クライアントIDの値です。これは、Auth0 Dashboardにあるアプリケーションの[Settings(設定)]で取得できます。 |
code_verifier | 認可URL(/authorize )に渡すcode_challenge の生成に使用された暗号的にランダムなキーです。 |
code | 前回の認可呼び出しで受け取ったauthorization_code です。 |
redirect_uri | 前のセクションで/authorize に渡されたredirect_uri とURLが一致しなければなりません。 |
トークンURLからの応答は、以下を含みます。
{
"access_token": "eyJz93a...k4laUWw",
"refresh_token": "GEbRxBN...edjnXbL",
"id_token": "eyJ0XAi...4faeEoQ",
"token_type": "Bearer",
"expires_in":86400
}
Was this helpful?
access_token:
audience
で指定されたAPIのアクセストークンrefresh_token:リフレッシュトークンは、
offline_access
スコープを含め、DashboardでAPIの[Allow Offline Access(オフラインアクセスの許可)]を有効にした場合にのみ表示されます。id_token:ユーザープロファイル情報が入ったIDトークンJWT
token_type:トークンのタイプを含む文字列で、常にベアラートークンです。
expires_in:アクセストークンの有効期限が切れるまでの秒数。
APIの呼び出しとユーザープロファイルの取得に使用するために、上記の資格情報をローカルストレージに保存しておく必要があります。
ユーザープロファイルの取得
ユーザープロファイルを取得するために、モバイルアプリケーションはJWTライブラリーの1つを使用して、IDトークンをデコードすることができます。これは、署名を検証して、トークンのクレームを検証することで行えます。IDトークンを検証したら、ユーザー情報を含んだペイロードにアクセスできます。
{
"email_verified": false,
"email": "test.account@userinfo.com",
"clientID": "q2hnj2iu...",
"updated_at": "2016-12-05T15:15:40.545Z",
"name": "test.account@userinfo.com",
"picture": "https://s.gravatar.com/avatar/dummy.png",
"user_id": "auth0|58454...",
"nickname": "test.account",
"created_at": "2016-12-05T11:16:59.640Z",
"sub": "auth0|58454..."
}
Was this helpful?
スコープに基づいた条件付きUI要素の表示
ユーザーのscope
に基づいて、特定のUI要素を表示または非表示にしたい場合があります。ユーザーに発行されたスコープを特定するには、ユーザーが認証されたときに付与されたscope
を調べる必要があります。これは、すべてのスコープを含んでいる文字列であるため、この文字列を調べて必要なscope
が含まれているかどうかを調べる必要があります。そしてそれに基づいて、特定のUI要素を表示するかどうかを決定できます。
APIの呼び出し
APIから安全なリソースにアクセスするには、認証されたユーザーのアクセストークンを、送信される要求に入れる必要があります。これには、Bearer
スキームを使用して、Authorization
ヘッダー内でアクセストークンを送る必要があります。
トークンの更新
アクセストークンをリフレッシュするには、認可結果のリフレッシュトークンを使用して、/oauth/token
エンドポイントへのPOST
要求を実行します。
リフレッシュトークンは、offline_access
スコープを先行の認可要求に含め、DashboardでAPIの[Allow Offline Access(オフラインアクセスの許可)]を有効にした場合にのみ表示されます。
要求には以下を含める必要があります。
curl --request POST \
--url 'https://{yourDomain}/oauth/token' \
--header 'content-type: application/x-www-form-urlencoded'
Was this helpful?
var client = new RestClient("https://{yourDomain}/oauth/token");
var request = new RestRequest(Method.POST);
request.AddHeader("content-type", "application/x-www-form-urlencoded");
IRestResponse response = client.Execute(request);
Was this helpful?
package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
url := "https://{yourDomain}/oauth/token"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Add("content-type", "application/x-www-form-urlencoded")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := ioutil.ReadAll(res.Body)
fmt.Println(res)
fmt.Println(string(body))
}
Was this helpful?
HttpResponse<String> response = Unirest.post("https://{yourDomain}/oauth/token")
.header("content-type", "application/x-www-form-urlencoded")
.asString();
Was this helpful?
var axios = require("axios").default;
var options = {
method: 'POST',
url: 'https://{yourDomain}/oauth/token',
headers: {'content-type': 'application/x-www-form-urlencoded'}
};
axios.request(options).then(function (response) {
console.log(response.data);
}).catch(function (error) {
console.error(error);
});
Was this helpful?
#import <Foundation/Foundation.h>
NSDictionary *headers = @{ @"content-type": @"application/x-www-form-urlencoded" };
NSData *postData = [[NSData alloc] initWithData:[@"undefined" dataUsingEncoding:NSUTF8StringEncoding]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://{yourDomain}/oauth/token"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:10.0];
[request setHTTPMethod:@"POST"];
[request setAllHTTPHeaderFields:headers];
[request setHTTPBody:postData];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"%@", error);
} else {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
NSLog(@"%@", httpResponse);
}
}];
[dataTask resume];
Was this helpful?
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://{yourDomain}/oauth/token",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"content-type: application/x-www-form-urlencoded"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}
Was this helpful?
import http.client
conn = http.client.HTTPSConnection("")
headers = { 'content-type': "application/x-www-form-urlencoded" }
conn.request("POST", "/{yourDomain}/oauth/token", headers=headers)
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))
Was this helpful?
require 'uri'
require 'net/http'
require 'openssl'
url = URI("https://{yourDomain}/oauth/token")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Post.new(url)
request["content-type"] = 'application/x-www-form-urlencoded'
response = http.request(request)
puts response.read_body
Was this helpful?
import Foundation
let headers = ["content-type": "application/x-www-form-urlencoded"]
let postData = NSData(data: "undefined".data(using: String.Encoding.utf8)!)
let request = NSMutableURLRequest(url: NSURL(string: "https://{yourDomain}/oauth/token")! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error)
} else {
let httpResponse = response as? HTTPURLResponse
print(httpResponse)
}
})
dataTask.resume()
Was this helpful?
パラメーター | 説明 |
---|---|
grant_type | refresh_token に設定する必要があります。 |
client_id | Auth0クライアントIDの値。これはAuth0 Dashboardにある[Application(アプリケーション)]の[Settings(設定)]で取得できます。 |
refresh_token | 前回の認証結果から使用するリフレッシュトークン。 |
応答には、新しいアクセストークンが含まれます。
{
"access_token": "eyJz93a...k4laUWw",
"refresh_token": "GEbRxBN...edjnXbL",
"id_token": "eyJ0XAi...4faeEoQ",
"token_type": "Bearer",
"expires_in":86400
}
Was this helpful?