ユーザーデータの保存

Auth0ではテナントのユーザー情報をホストされたクラウドデータベースに保存しますが、独自の外部カスタムデータベースにユーザーデータを保存することもできます。

Auth0が認証に使用する基本情報以外のユーザーデータを保存するには、Auth0のデータストアまたはカスタムデータベースを使用できます。ただし、その追加データを認証目的で使用する場合は、ユーザーデータをAuth0 Management Dashboardで管理できるよう、Auth0のデータストアを使用することを推奨します。

外部データベースとAuth0データストア

Auth0データストアは認証データ用にカスタマイズされています。デフォルトのユーザー情報以外のデータ保存は、以下の理由により、限られた場合にのみ行ってください。

  • スケーラビリティ:Auth0データストアの拡張性は限られており、アプリケーションのデータが適切な限度を超える可能性があります。外部データベースを使用することにより、Auth0データストアをシンプルに保ちつつ、より効率的な外部データベースに追加データを格納することができます。

  • パフォーマンス:認証データへのアクセス頻度は、他のデータよりも低い傾向があります。Auth0データストアは高頻度の使用には最適化されていないため、より頻繁に取得されるデータは別の場所に保存する必要があります。

  • フレキシビリティ:Auth0データストアはユーザープロファイルと関連するメタデータのみを格納するように構築されているので、データベースで実行できるアクションが制限されています。そのため、他のデータには異なるデータベースを使用することで、データを適切に管理することができます。

ユーザー認証をアウトソーシングする場合、通常は独自のユーザー/パスワードテーブルを維持する必要はありません。しかし、アプリケーションデータを認証されたユーザーに関連付けた方が良い場合があります。

  • たとえば、Auth0によって認証された各ユーザーを一覧にしたユーザーテーブルを作成することができます。ユーザーがログインするたびに、このテーブルで該当するユーザーを検索します。ユーザーが存在しない場合には、新しいレコードを作成します。存在する場合には、すべてのフィールドを更新して、基本的にすべてのユーザーデータのローカルコピーを保存します。

  • もしくは代わりに、ユーザーに関連付けられたデータのある各テーブル/コレクションにユーザー識別子を保存することもできます。これは、小さなアプリケーションに適したシンプルな方法です。

ユーザーデータ保存のシナリオ例

Auth0ではサンプルアプリとしてモバイル音楽アプリケーションを提供しており、Auth0と外部カスタムデータベースを使用したときのエンドツーエンドのユーザーエクスペリエンスを確認できます。このサンプルアプリはAuth0 iOSのシードプロジェクトを活用して作成されたiOSアプリです。バックエンドはNode.js APIを使用しています。

このアプリケーションの全体的な構造のビジュアライゼーションについては、モバイル+APIアーキテクチャシナリオを参照してください。

メタデータ

アプリメタデータ

モバイル音楽アプリケーションの以下のデータポイントを保存する場所としては、app_metadataが適しています。

  • ユーザーのサブスクリプションプラン

  • おすすめのプレイリストを編集するユーザーの権限の有無

この2つのデータポイントは、ユーザーが直接変更できてはならないため、user_metadataではなくapp_metadataに保存しなければなりません。

ユーザーメタデータ

モバイル音楽アプリケーションの以下のデータポイントを保存する場所としては、user_metadataが適しています。

  • アプリケーションの環境設定

  • ログイン時のアプリのエクスペリエンスを変更するためにユーザーが選択した詳細

app_metadataのデータポイントとは異なり、user_metadataに保存されたこれらの情報はユーザーが簡単に変更できます。

ユーザーがログイン時に目にし、アプリの他のユーザーに表示されるユーザー名、displayNameをユーザーが変更できるようにすることもできます。

ユーザーが選択した識別子をログイン時に表示するには、ルールを使用してuser.user_metadata値を取得します。

function(user, context, callback){
  user.user_metadata = user.user_metadata || {};
  user.user_metadata.displayName = user.user_metadata.displayName || "user";

  auth0.users.updateUserMetadata(user.user_id, user.user_metadata)
    .then(function(){
      callback(null, user, context);
    })
    .catch(function(err){
      callback(err);
    });
}

Was this helpful?

/

ユーザーが自身のdisplayNameを変更するために使用する画面はこのようになります。

表示名を更新するオプションがあるiOSアプリ設定画面です。

変更内容をデータベースに保存するため、アプリケーションがManagement APIユーザー取得エンドポイントを呼び出して適切なユーザーを識別します。


curl --request GET \
  --url https://%7ByourAccount%7D.auth0.com/api/v2/users/user_id \
  --header 'authorization: Bearer {yourIdToken}'

Was this helpful?

/
var client = new RestClient("https://%7ByourAccount%7D.auth0.com/api/v2/users/user_id");
var request = new RestRequest(Method.GET);
request.AddHeader("authorization", "Bearer {yourIdToken}");
IRestResponse response = client.Execute(request);

Was this helpful?

/
package main

import (
	"fmt"
	"net/http"
	"io/ioutil"
)

func main() {

	url := "https://%7ByourAccount%7D.auth0.com/api/v2/users/user_id"

	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("authorization", "Bearer {yourIdToken}")

	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.get("https://%7ByourAccount%7D.auth0.com/api/v2/users/user_id")
  .header("authorization", "Bearer {yourIdToken}")
  .asString();

Was this helpful?

/
var axios = require("axios").default;

var options = {
  method: 'GET',
  url: 'https://%7ByourAccount%7D.auth0.com/api/v2/users/user_id',
  headers: {authorization: 'Bearer {yourIdToken}'}
};

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 = @{ @"authorization": @"Bearer {yourIdToken}" };

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://%7ByourAccount%7D.auth0.com/api/v2/users/user_id"]
                                                       cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                   timeoutInterval:10.0];
[request setHTTPMethod:@"GET"];
[request setAllHTTPHeaderFields:headers];

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://%7ByourAccount%7D.auth0.com/api/v2/users/user_id",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_HTTPHEADER => [
    "authorization: Bearer {yourIdToken}"
  ],
]);

$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 = { 'authorization': "Bearer {yourIdToken}" }

conn.request("GET", "%7ByourAccount%7D.auth0.com/api/v2/users/user_id", 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://%7ByourAccount%7D.auth0.com/api/v2/users/user_id")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

request = Net::HTTP::Get.new(url)
request["authorization"] = 'Bearer {yourIdToken}'

response = http.request(request)
puts response.read_body

Was this helpful?

/
import Foundation

let headers = ["authorization": "Bearer {yourIdToken}"]

let request = NSMutableURLRequest(url: NSURL(string: "https://%7ByourAccount%7D.auth0.com/api/v2/users/user_id")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers

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?

/

その後、ユーザー更新エンドポイントが呼び出され、user_metadataフィールドが更新されます。


curl --request PATCH \
  --url 'https://{yourDomain}/api/v2/users/user_id' \
  --header 'authorization: Bearer {yourAccessToken}' \
  --header 'content-type: application/json' \
  --data '{"user_metadata": {"displayName": "J-vald3z"}'

Was this helpful?

/
var client = new RestClient("https://{yourDomain}/api/v2/users/user_id");
var request = new RestRequest(Method.PATCH);
request.AddHeader("authorization", "Bearer {yourAccessToken}");
request.AddHeader("content-type", "application/json");
request.AddParameter("application/json", "{\"user_metadata\": {\"displayName\": \"J-vald3z\"}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);

Was this helpful?

/
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io/ioutil"
)

func main() {

	url := "https://{yourDomain}/api/v2/users/user_id"

	payload := strings.NewReader("{\"user_metadata\": {\"displayName\": \"J-vald3z\"}")

	req, _ := http.NewRequest("PATCH", url, payload)

	req.Header.Add("authorization", "Bearer {yourAccessToken}")
	req.Header.Add("content-type", "application/json")

	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.patch("https://{yourDomain}/api/v2/users/user_id")
  .header("authorization", "Bearer {yourAccessToken}")
  .header("content-type", "application/json")
  .body("{\"user_metadata\": {\"displayName\": \"J-vald3z\"}")
  .asString();

Was this helpful?

/
var axios = require("axios").default;

var options = {
  method: 'PATCH',
  url: 'https://{yourDomain}/api/v2/users/user_id',
  headers: {authorization: 'Bearer {yourAccessToken}', 'content-type': 'application/json'},
  data: '{"user_metadata": {"displayName": "J-vald3z"}'
};

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 = @{ @"authorization": @"Bearer {yourAccessToken}",
                           @"content-type": @"application/json" };

NSData *postData = [[NSData alloc] initWithData:[@"{"user_metadata": {"displayName": "J-vald3z"}" dataUsingEncoding:NSUTF8StringEncoding]];

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://{yourDomain}/api/v2/users/user_id"]
                                                       cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                   timeoutInterval:10.0];
[request setHTTPMethod:@"PATCH"];
[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}/api/v2/users/user_id",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "PATCH",
  CURLOPT_POSTFIELDS => "{\"user_metadata\": {\"displayName\": \"J-vald3z\"}",
  CURLOPT_HTTPHEADER => [
    "authorization: Bearer {yourAccessToken}",
    "content-type: application/json"
  ],
]);

$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 = "{\"user_metadata\": {\"displayName\": \"J-vald3z\"}"

headers = {
    'authorization': "Bearer {yourAccessToken}",
    'content-type': "application/json"
    }

conn.request("PATCH", "/{yourDomain}/api/v2/users/user_id", 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}/api/v2/users/user_id")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

request = Net::HTTP::Patch.new(url)
request["authorization"] = 'Bearer {yourAccessToken}'
request["content-type"] = 'application/json'
request.body = "{\"user_metadata\": {\"displayName\": \"J-vald3z\"}"

response = http.request(request)
puts response.read_body

Was this helpful?

/
import Foundation

let headers = [
  "authorization": "Bearer {yourAccessToken}",
  "content-type": "application/json"
]

let postData = NSData(data: "{"user_metadata": {"displayName": "J-vald3z"}".data(using: String.Encoding.utf8)!)

let request = NSMutableURLRequest(url: NSURL(string: "https://{yourDomain}/api/v2/users/user_id")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "PATCH"
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?

/

{yourAccessToken}Management APIアクセストークンに置き換える必要があります。

ユーザーデータの権限ルール

ルールを使用して、ユーザーがおすすめのプレイリストを編集できるかどうかを決める権限を実装します。

プレイリスト編集者ロールを割り当てる

1つめのルールは、Node APIに要求を送信し、Node APIはHerokuに接続されたデータベースにクエリを実行して、ユーザーのプレイリストの再生回数を確認します。回数が100以上の場合は、app_metadataroles配列の値としてplaylist_editorが割り当てられます。

function (user, context, callback) {

  var request = require('request');

  user.app_metadata = user.app_metadata || {};
  user.app_metadata.roles = user.roles || [];

  var CLIENT_SECRET = configuration.AUTH0_CLIENT_SECRET;
  var CLIENT_ID = configuration.AUTH0_CLIENT_ID;

  var scope = {
    user_id: user.user_id,
    email: user.email,
    name: user.name
  };

  var options = {
    subject: user.user_id,
    expiresInMinutes: 600,
    audience: CLIENT_ID,
    issuer: 'https://example.auth0.com'
  };

  var id_token = jwt.sign(scope, CLIENT_SECRET, options);

  var auth = 'Bearer ' + id_token;

  request.get({
    url: 'https://example.com/playlists/getPlays',
    headers: {
       'Authorization': auth,
      'Content-Type': 'text/html'
    },
    timeout: 15000
  }, function(err, response, body){
    if (err)
      return callback(new Error(err));
    var plays = parseInt(body, 10);

    if (plays >= 100 && user.roles.indexOf('playlist_editor') < 0){
      user.app_metadata.roles.push('playlist_editor');
      auth0.users.updateAppMetadata(user.user_id, user.app_metadata)
        .then(function(){
          callback(null, user, context);
        })
        .catch(callback);
    }

    else if (plays < 100 && user.roles.indexOf('playlist_editor') >= 0){
      user.app_metadata.roles = [];
      auth0.users.updateAppMetadata(user.user_id, user.app_metadata)
        .then(function(){
          callback(null, user, context);
        })
        .catch(callback);
    }
    else{
      callback(null, user, context);
    }

  });

}

Was this helpful?

/

スコープパラメーターがロールを指定する

2つめのルールは、app_metadataフィールドを取得して、ユーザーオブジェクトのフィールドにroles配列を割り当てます。これにより、アプリケーションでapp_metadataを呼び出すことなくアクセスできるようになります。これでscopeパラメーターは、ユーザーオブジェクトのapp_metadataのすべてを含めることなく、ユーザーのログイン時にrolesを指定できます。

function(user, context, callback) {
   if (user.app_metadata) {
      user.roles = user.app_metadata.roles;
   }
   user.roles = user.roles || [];
   callback(null, user, context);
}

Was this helpful?

/

この2つのルールを実装すると、アプリはユーザーがプレイリスト編集者かどうかを判断でき、ロールに応じてウェルカム画面を変更します。ユーザーのapp_metadataに保存されているroles配列にplaylist_editorがある場合、ユーザーにはサインイン後にEDITOR(編集者)向けの画面が表示されます。

Example of user profile page with editor role.

ユーザーの音楽をユーザーに関連付ける

このアプリでは、ユーザーの音楽をユーザーに関連付ける必要がありますが、この情報は認証には必要ありません。この非認証情報をアプリケーションのバックエンドと統合された個別のデータベースに保存する方法を説明します。

ユーザーの一意の識別子はuser_idです。データベースのsongsテーブルにあるサンプル行がこちらです。

song_id songname user_id
1 No.1ヒットソング google-oauth2

Node.jsのバックエンドが、JSON Web Tokenを検証して、データベースからのユーザーの個人データ取得に関連付けられたURIへの要求を認証します。

トークンベースの認証とアプリケーションにJWTを実装する方法

こちらは、Node.jsシードプロジェクトからの、JWT検証を実装するコードです。

var genres = require('./routes/genres');
var songs = require('./routes/songs');
var playlists = require('./routes/playlists');
var displayName = require('./routes/displayName');

var authenticate = jwt({
  secret: process.env.AUTH0_CLIENT_SECRET,
  audience: process.env.AUTH0_CLIENT_ID
});

app.use('/genres', authenticate, genres);
app.use('/songs', authenticate, songs);
app.use('/playlists', authenticate, playlists);
app.use('/displayName', authenticate, displayName);

Was this helpful?

/

アプリケーションからのさまざまなデータ要求を処理する機能性を追加できます。たとえば、/secured/getFavGenreへのGET要求を受信したら、APIがqueryGenre()関数を呼び出し、データベースを照会してユーザーの好きなジャンルで応答します。

@IBAction func getGenre(sender: AnyObject) {
        let request = buildAPIRequest("/genres/getFav", type:"GET")
        let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {[unowned self](data, response, error) in
            let genre = NSString(data: data!, encoding: NSUTF8StringEncoding)
            dispatch_async(dispatch_get_main_queue(), {
                self.favGenre.text = "Favorite Genre:  \(genre!)"
            })
        }
        task.resume()
    }

Was this helpful?

/

buildAPIRequest()関数は、要求のパスとHTTPメソッドをパラメーターとして受け取り、HerokuでホストされているNode.js APIのベースURLを使用して要求を構築します。

アプリケーションでは、getGenre()関数がAPIに要求を送信し、アプリのインターフェイスを変更して/genres/getFavへの要求の応答を表示します。バックエンドはこのアクションに必要なデータをqueryGenre()関数を使用して取得し、結果をアプリケーションに返します。

function queryGenre(user_id, res){

  db.connect(process.env.DATABASE_URL, function(err, client) {
  if (err) throw err;

  client
    .query('SELECT fav_genre as value FROM user_data WHERE user_id = $1', [user_id], function(err, result) {

      if(err) {
        return console.error('error running query', err);
      }
      res.send(result.rows[0].value);
    });
  });

};

Was this helpful?

/

もっと詳しく