メインコンテンツへスキップ
Auth0 は、テナントのユーザー情報をホスト型クラウドデータベースに保存します。ユーザーデータを独自の外部カスタムデータベースに保存することもできます。 Auth0 が認証に使用する基本情報を超えるユーザーデータを保存するには、Auth0 のデータストアまたはカスタムデータベースを使用できます。ただし、その追加データを認証に使用する場合は、Auth0 Management Dashboard からユーザーデータを管理できるため、Auth0 のデータストアを使用することをお勧めします。

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

Auth0データストアは、認証データ向けに設計されています。デフォルトのユーザー情報を超えるデータの保存は、限定的なケースにとどめるべきです。理由は次のとおりです。
  • スケーラビリティ: Auth0データストアのスケーラビリティには限界があり、アプリケーションのデータ量が適切な上限を超える可能性があります。外部データベースを使用すれば、Auth0データストアはシンプルに保ちつつ、追加データはより効率的な外部データベースに保存できます。
  • パフォーマンス: 認証データは、他のデータに比べてアクセス頻度が低いことが一般的です。Auth0データストアは高頻度アクセス向けには最適化されていないため、より頻繁に取得する必要があるデータは別の場所に保存するべきです。
  • 柔軟性: Auth0データストアは、ユーザープロフィールとそれに関連するメタデータのみを扱うように設計されているため、データベースに対して実行できる操作には制限があります。他のデータを別のデータベースに保存すれば、用途に応じて適切にデータを管理でき、Auth0のを使わずに直接呼び出しを実行できます。
ユーザー認証を外部に委ねる場合、通常は独自の users/passwords テーブルを維持する必要はありません。それでも、認証済みユーザーにアプリケーションデータを関連付けたい場合はあります。
  • たとえば、Auth0で認証された各ユーザーを一覧するUsersテーブルを用意できます。ユーザーがログインするたびに、そのテーブルで該当ユーザーを検索します。ユーザーが存在しなければ新しいレコードを作成し、存在する場合はすべてのフィールドを更新して、実質的にすべてのユーザーデータのローカルコピーを維持します。
  • あるいは、ユーザー関連データを持つ各テーブルやコレクションにユーザー識別子を保存する方法もあります。これは、比較的小規模なアプリケーションに適した、よりシンプルな実装です。

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

Auth0 では、カスタムの外部データベースと Auth0 を組み合わせて使用する場合のエンドツーエンドのユーザー体験を示す、モバイル音楽アプリのサンプルを提供しています。このサンプルアプリは、Auth0 iOS seed project を使用して作成された iOS アプリです。バックエンドには Node.js API を使用します。 アプリケーション全体の構造を視覚的に確認するには、Mobile + API architecture scenario を参照してください。

メタデータ

アプリメタデータ

モバイル音楽アプリケーションでは、次のデータ項目を app_metadata に保存するのが適切です。
  • ユーザーのサブスクリプションプラン
  • 注目のプレイリストを編集できるかどうか
これら 2 つのデータ項目は、ユーザーが直接変更できるものではないため、user_metadata ではなく app_metadata に保存する必要があります。

ユーザーメタデータ

モバイル音楽アプリケーションでは、次のようなデータを user_metadata に保存するのが適切です。
  • アプリケーションの設定
  • ログイン時のアプリ体験を変えるために、ユーザーが選択した項目
app_metadata に保存するデータとは異なり、user_metadata に保存されたこれらの値は、ユーザー自身が簡単に変更できます。 たとえば、ユーザーが displayName を変更できるようにできます。これは、ログイン時にユーザー本人に表示され、アプリの他のユーザーにも表示される名前です。 ユーザーがログインするたびに、ユーザーが選択した識別子を表示するには、Rule を使用して 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);
    });
}
ユーザーが displayName を変更する際に使用する画面は次のとおりです。
表示名を更新するオプションがある iOS アプリの設定画面。
変更をデータベースに保存するには、アプリケーションは適切なユーザーを特定するために、Management API の Get a User エンドポイントを呼び出します。 次に、user_metadata フィールドを更新するために Update a User エンドポイントを呼び出します。 {yourAccessToken}Management API アクセストークン に置き換えてください。

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

ユーザーが注目のプレイリストを編集できるかどうかを制御する権限を実装するには、Rules を使用します。

Playlist Editor ロールを割り当てる

最初のRuleは Node API にリクエストを送信し、その 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);
    }

  });

}

scope パラメーターでロールを指定する

2 番目の Rule では、app_metadata フィールドを取得し、roles 配列をユーザーオブジェクト内のフィールドに割り当てます。これにより、アプリケーション側で app_metadata を参照しなくてもアクセスできるようになります。すると、ユーザーのログイン時に、ユーザーオブジェクトへ app_metadata の内容をすべて含めなくても、scope パラメーターで roles を指定できます。
function(user, context, callback) {
   if (user.app_metadata) {
      user.roles = user.app_metadata.roles;
   }
   user.roles = user.roles || [];
   callback(null, user, context);
}
これら 2 つのルールを実装すると、アプリはユーザーがプレイリストエディターかどうかを判別し、それに応じてようこそ画面を変更します。ユーザーの app_metadata に保存されている roles 配列に playlist_editor が含まれている場合、サインイン後、ユーザーは EDITOR として迎えられます。
エディターロールを持つユーザープロフィールページの例。

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

ユーザーの音楽をそのユーザーに関連付ける必要がありますが、この情報は認証に必須ではありません。ここでは、アプリケーションのバックエンドと連携した別のデータベースにこの情報を保存する方法を説明します。 ユーザーの一意の識別子は user_id で、authResult 内の idTokenPayload オブジェクトの sub プロパティです。以下は、データベース内の songs テーブルの行の例です。
song_idsongnameuser_id
1Number One Hitgoogle-oauth2
Node.js バックエンドでは、 を検証することで、データベースからユーザーの個人データを取得する URI へのリクエストを認証します。 トークンベースの認証と、Applications で 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);
アプリケーションからのさまざまなデータリクエストを処理する機能を追加できます。たとえば、/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()
    }
buildAPIRequest() 関数は、リクエストのパスと HTTP メソッドをパラメーターとして受け取り、Heroku でホストされている Node.js API のベース URL を使用してリクエストを構築します。 Application では、getGenre() 関数が API にリクエストを送信し、/genres/getFav へのリクエストのレスポンスが表示されるようにアプリのインターフェースを更新します。バックエンドは、この処理に必要なデータを queryGenre() 関数で取得し、その結果を Application に返します。
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);
    });
  });

};

詳細