> ## Documentation Index
> Fetch the complete documentation index at: https://docs-dev-docs-event-stream-action-templates.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# ユーザーデータの保存

> Auth0データストアと外部データベースを使用したユーザーデータ保存の違いを説明します。

export const AuthCodeGroup = ({children, dropdown}) => {
  const [processedChildren, setProcessedChildren] = useState(children);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      unsubscribe = window.autorun(() => {
        const processChildren = node => {
          if (typeof node === "string") {
            let processedNode = node;
            for (const [key, value] of window.rootStore.variableStore.values.entries()) {
              const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
              processedNode = processedNode.replaceAll(new RegExp(escapedKey, "g"), value);
            }
            return processedNode;
          } else if (Array.isArray(node)) {
            return node.map(processChildren);
          } else if (node && node.props && node.props.children) {
            return {
              ...node,
              props: {
                ...node.props,
                children: processChildren(node.props.children)
              }
            };
          }
          return node;
        };
        setProcessedChildren(processChildren(children));
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  return <CodeGroup dropdown={dropdown}>{processedChildren}</CodeGroup>;
};

export const AuthCodeBlock = ({filename, icon, language, highlight, children}) => {
  const [displayText, setDisplayText] = useState(children);
  const [copyText, setCopyText] = useState(children);
  const wrapperRef = React.useRef(null);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      if (!window.autorun || !window.rootStore) {
        return;
      }
      unsubscribe = window.autorun(() => {
        let processedChildrenForDisplay = children;
        let processedChildrenForCopy = children;
        for (const [key, value] of window.rootStore.variableStore.values.entries()) {
          const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
          let displayValue = value;
          if (key === "{yourClientSecret}" && value !== "{yourClientSecret}") {
            displayValue = value.substring(0, 3) + "*****MASKED*****";
          }
          processedChildrenForDisplay = processedChildrenForDisplay.replaceAll(new RegExp(escapedKey, "g"), displayValue);
          processedChildrenForCopy = processedChildrenForCopy.replaceAll(new RegExp(escapedKey, "g"), value);
        }
        setDisplayText(processedChildrenForDisplay);
        setCopyText(processedChildrenForCopy);
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  useEffect(() => {
    if (!wrapperRef.current) return;
    const originalWriteText = navigator.clipboard.writeText.bind(navigator.clipboard);
    let isOverriding = false;
    const handleClick = e => {
      const button = e.target.closest('[data-testid="copy-code-button"]');
      if (!button || !wrapperRef.current.contains(button)) return;
      isOverriding = true;
      navigator.clipboard.writeText = text => {
        if (isOverriding) {
          isOverriding = false;
          navigator.clipboard.writeText = originalWriteText;
          return originalWriteText(copyText);
        }
        return originalWriteText(text);
      };
      setTimeout(() => {
        if (isOverriding) {
          isOverriding = false;
          navigator.clipboard.writeText = originalWriteText;
        }
      }, 100);
    };
    const wrapper = wrapperRef.current;
    wrapper.addEventListener('click', handleClick, true);
    return () => {
      wrapper.removeEventListener('click', handleClick, true);
      if (navigator.clipboard.writeText !== originalWriteText) {
        navigator.clipboard.writeText = originalWriteText;
      }
    };
  }, [copyText]);
  return <div ref={wrapperRef}>
      <CodeBlock filename={filename} icon={icon} language={language} lines highlight={highlight}>
        {displayText}
      </CodeBlock>
    </div>;
};

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

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

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

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

* **スケーラビリティ**：Auth0データストアの拡張性は限られており、アプリケーションのデータが適切な限度を超える可能性があります。外部データベースを使用することにより、Auth0データストアをシンプルに保ちつつ、より効率的な外部データベースに追加データを格納することができます。
* **パフォーマンス**：認証データへのアクセス頻度は、他のデータよりも低い傾向があります。Auth0データストアは高頻度の使用には最適化されていないため、より頻繁に取得されるデータは別の場所に保存する必要があります。
* **フレキシビリティ**：Auth0データストアはユーザープロファイルと関連するメタデータのみを格納するように構築されているので、データベースで実行できるアクションが制限されています。そのため、他のデータには異なるデータベースを使用することで、データを適切に管理することができます。

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

* たとえば、Auth0によって認証された各ユーザーを一覧にしたユーザーテーブルを作成することができます。ユーザーがログインするたびに、このテーブルで該当するユーザーを検索します。ユーザーが存在しない場合には、新しいレコードを作成します。存在する場合には、すべてのフィールドを更新して、基本的にすべてのユーザーデータのローカルコピーを保存します。
* もしくは代わりに、ユーザーに関連付けられたデータのある各テーブル/コレクションにユーザー識別子を保存することもできます。これは、小さなアプリケーションに適したシンプルな方法です。

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

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

このアプリケーションの全体的な構造のビジュアライゼーションについては、[モバイル＋APIアーキテクチャシナリオ](/docs/architecture-scenarios/application/mobile-api)を参照してください。

### メタデータ

#### アプリメタデータ

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

* ユーザーのサブスクリプションプラン
* おすすめのプレイリストを編集するユーザーの権限の有無

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

#### ユーザーメタデータ

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

* アプリケーションの環境設定
* ログイン時のアプリのエクスペリエンスを変更するためにユーザーが選択した詳細

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

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

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

```javascript lines theme={null}
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`を変更するために使用する画面はこのようになります。

<Frame>
  <img src="https://mintcdn.com/docs-dev-docs-event-stream-action-templates/itywf6vBRFUC2Nea/docs/images/ja-jp/cdy7uua7fh8z/33YxGQKYztHY3OM6mxJ4jN/0a06103e6b6455c2e1962a1f738b663a/4-settings.png?fit=max&auto=format&n=itywf6vBRFUC2Nea&q=85&s=ec81cf955ec2f32d6bd068aa58825b6d" alt="表示名を更新するオプションがあるiOSアプリ設定画面です。" width="300" height="534" data-path="docs/images/ja-jp/cdy7uua7fh8z/33YxGQKYztHY3OM6mxJ4jN/0a06103e6b6455c2e1962a1f738b663a/4-settings.png" />
</Frame>

変更内容をデータベースに保存するため、アプリケーションが<Tooltip data-tooltip-id="react-containers-DefinitionTooltip-0" href="/docs/ja-jp/glossary?term=management-api" tip="Management API: 顧客が管理タスクを実行できるようにするための製品。" cta="用語集の表示">Management API</Tooltip>の[ユーザー取得](/docs/api/management/v2#!/Users/get_users_by_id)エンドポイントを呼び出して適切なユーザーを識別します。

<AuthCodeGroup>
  ````bash cURL lines theme={null}
  curl --request GET \
  --url https://%7ByourAccount%7D.auth0.com/api/v2/users/user_id \
  --header 'authorization: Bearer {yourIdToken}'
  ``` lines
  ```csharp C# lines
  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);
  ````

  ````go Go lines theme={null}
  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))
  }
  ``` lines
  ```java Java lines
  HttpResponse response = Unirest.get("https://%7ByourAccount%7D.auth0.com/api/v2/users/user_id")
  .header("authorization", "Bearer {yourIdToken}")
  .asString();
  ````

  ````javascript Node.JS lines theme={null}
  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);
  });
  ``` lines
  ```objc Obj-C lines
  #import 
  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];
  ````

  ````php PHP lines theme={null}
  $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;
  }
  ``` lines
  ```python Python lines
  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"))
  ````

  ````ruby Ruby lines theme={null}
  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
  ``` lines
  ```swift Swift lines
  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()
  ````
</AuthCodeGroup>

その後、[ユーザー更新](/docs/api/management/v2#!/Users/patch_users_by_id)エンドポイントが呼び出され、`user_metadata`フィールドが更新されます。

<AuthCodeGroup>
  ````bash cURL theme={null}
  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"}'
  ``` lines
  ```csharp C#
  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);
  ````

  ````go Go theme={null}
  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))
  }
  ``` lines
  ```java Java
  HttpResponse 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();
  ````

  ````javascript Node.JS theme={null}
  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);
  });
  ``` lines
  ```objc Obj-C
  #import 
  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];
  ````

  ````php PHP theme={null}
  $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;
  }
  ``` lines
  ```python Python
  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"))
  ````

  ````ruby Ruby theme={null}
  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
  ``` lines
  ```swift Swift
  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()
  ````
</AuthCodeGroup>

`{yourAccessToken}`を[Management APIアクセストークン](/docs/api/management/v2/concepts/tokens)に置き換える必要があります。

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

[ルール](/docs/ja-jp/customize/rules)を使用して、ユーザーがおすすめのプレイリストを編集できるかどうかを決める権限を実装します。

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

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

```javascript lines theme={null}
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);
    }

  });

}
```

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

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

```javascript lines theme={null}
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（編集者）\*\*向けの画面が表示されます。

<Frame>
  <img src="https://mintcdn.com/docs-dev-docs-event-stream-action-templates/1FU_hDyg-ytA7ilg/docs/images/ja-jp/cdy7uua7fh8z/1WEKC4eWvm8GPK2BlodGFk/3eb36843f22518ace800fc1507bd5816/3-home.png?fit=max&auto=format&n=1FU_hDyg-ytA7ilg&q=85&s=888bdb0d47c4d3510c3ffcb1fea99b82" alt="Example of user profile page with editor role." width="300" height="534" data-path="docs/images/ja-jp/cdy7uua7fh8z/1WEKC4eWvm8GPK2BlodGFk/3eb36843f22518ace800fc1507bd5816/3-home.png" />
</Frame>

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

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

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

| song\_id | songname   | user\_id      |
| -------- | ---------- | ------------- |
| 1        | No.1ヒットソング | google-oauth2 |

Node.jsのバックエンドが、<Tooltip data-tooltip-id="react-containers-DefinitionTooltip-2" href="/docs/ja-jp/glossary?term=json-web-token" tip="JSON Web Token（JWT）: 二者間のクレームを安全に表現するために使用される標準IDトークン形式（および多くの場合、アクセストークン形式）。" cta="用語集の表示">JSON Web Token</Tooltip>を検証して、データベースからのユーザーの個人データ取得に関連付けられたURIへの要求を認証します。

[トークンベースの認証とアプリケーションにJWTを実装する方法](/docs/tokens/concepts/jwts)

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

```javascript lines theme={null}
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()`関数を呼び出し、データベースを照会してユーザーの好きなジャンルで応答します。

```swift lines theme={null}
@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を使用して要求を構築します。

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

```javascript lines theme={null}
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);
    });
  });

};
```

## もっと詳しく

* [正規化ユーザープロファイル](/docs/ja-jp/manage-users/user-accounts/user-profiles/normalized-user-profiles)
* [正規化ユーザープロファイルスキーマ](/docs/ja-jp/manage-users/user-accounts/user-profiles/normalized-user-profile-schema)
* [ユーザープロファイルの例](/docs/ja-jp/manage-users/user-accounts/user-profiles/sample-user-profiles)
