Skip to content

Conversation

@takeyuweb
Copy link
Owner

@takeyuweb takeyuweb commented Feb 17, 2018

Auth0ログイン時のフォールバックを利用した、自動移行を試す

何が嬉しいか?

  • 既存のユーザーのパスワードリセットなしに、Auth0にユーザー情報を徐々に移していける
  • バッチ処理など不要

事前準備

https://auth0.com/docs/users/migrations/automatic

  • Connection の Database を以下のように設定
    • Use my own database => ON
      • Database Action Scripts
        • Login (後ろに貼るもの)
        • Get User (後ろに貼るもの)
    • Import Users to Auth0 => ON

再現手順

  1. Devise でユーザーを登録、ログイン
  2. Auth0 でログインする際、1でDeviseで登録したメールアドレス&パスワードを利用
  3. omniauth-auth0 でログイン成功、Auth0 にユーザーが追加されている

仕組み

https://auth0.com/docs/users/migrations/automatic

に書いてある。
Auth0でログインしようとした際に、未登録のメールアドレスの場合、事前に設定したLoginスクリプト(Node.js)が呼び出され、その中で旧来のデータベースに対して問い合わせを行う。
問い合わせ方法はNode.jsでかければなんでもよい。今回は問い合わせ用のAPIを作った。

Auth0で登録しようとした際も同様で、まずGet Userでチェックするようになる。

Database Action Scripts

Auth0の管理画面上でも設定・デバッグが可能だが、実際にはAuth0 Management APIを用いて設定するようにし、構成をコード化しておくべき。(#7

Login

function login (email, password, callback) {
  // This script should authenticate a user against the credentials stored in
  // your database.
  // It is executed when a user attempts to log in or immediately after signing
  // up (as a verification that the user was successfully signed up).
  // 
  // Everything returned by this script will be set as part of the user profile
  // and will be visible by any of the tenant admins. Avoid adding attributes 
  // with values such as passwords, keys, secrets, etc.
  //
  // The `password` parameter of this function is in plain text. It must be
  // hashed/salted to match whatever is stored in your database. For example:
  //
  //     var bcrypt = require('bcrypt@0.8.5');
  //     bcrypt.compare(password, dbPasswordHash, function(err, res)) { ... }
  //
  // There are three ways this script can finish:
  // 1. The user's credentials are valid. The returned user profile should be in
  // the following format: https://auth0.com/docs/user-profile/normalized
  //     var profile = {
  //       user_id: ..., // user_id is mandatory
  //       email: ...,
  //       [...]
  //     };
  //     callback(null, profile);
  // 2. The user's credentials are invalid
  //     callback(new WrongUsernameOrPasswordError(email, "my error message"));
  // 3. Something went wrong while trying to reach your database
  //     callback(new Error("my error message"));
  //
  // A list of Node.js modules which can be referenced is available here:
  //
  //    https://tehsis.github.io/webtaskio-canirequire/

  var https = require('https');
  
  // FIXME: GETだとアクセスログに残るなどの問題があるので実際はPOSTとかでやる
  var options = {
    hostname: 'rails-auth0-sample.herokuapp.com', 
    port: 443, 
    path: '/api/devise_users/login?email=' +  encodeURIComponent(email) + '&password=' + encodeURIComponent(password),
    headers: {
      'user-agent': 'auth0'
    }, 
    method: 'GET'
  };
  https.get(options, (res) => {
    res.setEncoding('utf8');
    var body = "";
    if (res.statusCode == 200) {
      res.on('data', (chunk) => body += chunk);
      res.on('end', () => { callback(null, JSON.parse(body)); });
    } else if (res.statusCode == 404) {
      callback(new WrongUsernameOrPasswordError(email, 'invalid password'));
    } else {
      callback(new Error('Error: ' + res.statusCode));
    }
  });
}

GetUser

function getByEmail (email, callback) {
  // This script should retrieve a user profile from your existing database,
  // without authenticating the user.
  // It is used to check if a user exists before executing flows that do not
  // require authentication (signup and password reset).
  //
  // There are three ways this script can finish:
  // 1. A user was successfully found. The profile should be in the following
  // format: https://auth0.com/docs/user-profile/normalized.
  //     callback(null, profile);
  // 2. A user was not found
  //     callback(null);
  // 3. Something went wrong while trying to reach your database:
  //     callback(new Error("my error message"));

  var https = require('https');
  
  var options = {
    hostname: 'rails-auth0-sample.herokuapp.com', 
    port: 443, 
    path: '/api/devise_users/get_user?email=' +  encodeURIComponent(email),
    headers: {
      'user-agent': 'simple-nodejs'
    }, 
    method: 'GET'
  };
  https.get(options, (res) => {
    res.setEncoding('utf8');
    var body = "";
    if (res.statusCode == 200) {
      res.on('data', (chunk) => body += chunk);
      res.on('end', () => { callback(null, JSON.parse(body)); });
    } else if (res.statusCode == 404) {
      callback(null);
    } else {
      callback(new Error('Error: ' + res.statusCode));
    }
  });

}

@takeyuweb takeyuweb force-pushed the samples/automatic-migration-from-devise branch from c85d1bf to 68e45d3 Compare February 17, 2018 10:13
@takeyuweb takeyuweb force-pushed the samples/automatic-migration-from-devise branch from 68e45d3 to 804db9b Compare February 17, 2018 10:16
@takeyuweb takeyuweb merged commit 325be38 into master Feb 17, 2018
@takeyuweb takeyuweb deleted the samples/automatic-migration-from-devise branch February 17, 2018 10:20
if user&.active_for_authentication?

# 認証を通ったらこのデータベースが持っているユーザー情報を返す
render json: user.auth0_data
Copy link
Owner Author

@takeyuweb takeyuweb Feb 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Database Action Script の Get User から呼び出すAPI
与えられたメールアドレスが登録済みか調べて返す
このAPIを呼び出すコードは Database Action Script で書くので、自由にできるし、なんなら直接データベースに接続してしまっても良い

user&.active_for_authentication?

# 認証を通ったらこのデータベースが持っているユーザー情報を返す
render json: user.auth0_data
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Database Action Script の Login から呼び出すAPI
与えられたメールアドレスとパスワードの組み合わせが正しいかを調べて、返す

if provider == 'auth0'
uid = session[:userinfo]['uid'] # ex) "auth0|5a84413df5c8213cb27be6cd" or "auth0|DeviseUser:1"
stripped_uid = uid.sub("#{provider}|", '')
DeviseUser.find_by_auth0_uid(stripped_uid)
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

もし Auth0 に移行してから、もともとのDeviseUserのどれに対応するんだっけ?ってなったら、uidから特定できる、という実験


# Auth0 への Automatic Migration 用の user_id を作る
def auth0_uid
"DeviseUser:#{id}"
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

別に id を渡しちゃってもよかったんだけど、他で登録されたユーザーとDeviseUserから移行したユーザーを明確に区別したかったのでこのように
メタデータ渡してもよいのかも?
https://auth0.com/docs/metadata

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#9 で "devise_user|#{id}" に変更
理由は #9 のPR参照

@takeyuweb takeyuweb restored the samples/automatic-migration-from-devise branch February 26, 2018 15:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants