Salesforce Developers Blog

Salesforceの認証を外部アプリから使う – OpenID Connect編

Avatar for Naoto TsukamotoNaoto Tsukamoto
Salesforceはアプリケーションの統合認証として利用できます。PythonアプリからOpenID Connect経由でSalesforce Customer IdentityをIDプロバイダーとして利用するサンプルコードをご紹介します。
Salesforceの認証を外部アプリから使う – OpenID Connect編
December 25, 2024

はじめに

みなさん、こんにちは。株式会社セールスフォース・ジャパンでPlatform Specialistをしている塚本と申します。

Salesforceは当然認証機能を持っています。その認証機能をSalesforce外部のアプリケーションで利用することができます。Salesforceという信頼性の高いプラットフォームにユーザ管理と認証を任せることができますし、多要素認証やソーシャルログイン、パスワードレスログインといった認証機能も組み込めます。

今回はSalesforceはWebサービスの認証基盤としても使えるんだぞ、ということでDeveloper Editionで利用できるExternal Identityライセンスを使ってOpenID ConnectでSalesforceの認証を利用するサンプルコードをご紹介します。

免責のお伝えです。本記事は 2024/12/20 時点の画面・提供機能でもって作成しています。その後の更新等で画面が変わっていたり機能が追加されている場合もあります。あらかじめご了承ください。最新情報については Help ページを参照いただきますよう、よろしくお願いいたします。

当サイトでは、最新かつ正確な情報の掲載を努めておりますが、正確性や安全性、効果等を保証するものではありません。本情報の内容(添付文書、リンク先などを含む)は、作成日時点でのものであり、予告なく変更される場合があります。また、当サイトの掲載内容によって損害等が生じた場合は、一切の責任を負いかねますので、ご利用の際はヘルプやリファレンスをご参照の上、お手元で十分検証をいただいたのち導入についてご判断くださいますようお願いいたします。

Salesforce Customer Identityを統合認証として採用するメリット

Salesforceを外部アプリケーションの認証で使うためのライセンスとしてはCustomer Identityがあります。Customer Identityは製品名であり、Salesforce組織の中のライセンスとしては External Identity がそれにあたります。Customer Identity はExperience Cloudの機能から認証機能のみを取り出した製品です。「認証機能のみ」とは言ってもユーザー管理に関する機能全般使えますし、カスタムオブジェクトも10個(内、8個はユーザー管理に関わるものに限定)使えますので、ユーザー管理ポータルサイトも運用可能です。

一般的な統合認証のメリット以外で、Salesforceを外部アプリケーションの統合認証として使うことのメリットは以下となります。

  • CRMであるSalesforce Platformで外部ユーザの認証・管理を���うことで顧客情報管理とダイレクトに紐づいたユーザー管理ができる
  • ユーザー管理プロセスをSalesforce Platformで実装できる
  • Experience Cloudのビルダーを活用できるのでUI含めてノーコード・ローコードで実装できる

また、Experience CloudはCustomer Identityの機能を内包していますので、Experience Cloudサイトを既に運用されている場合、追加でCustomer Identityを契約しなくてもExperience Cloudユーザーを外部アプリケーションの認証で使うことができます。

Salesforceの設定

接続アプリケーション作成

接続アプリケーションを作ってOpenIDプロバイダーとしての設定をします。(「設定」-「アプリケーションマネージャ」-「新規接続アプリケーション」)

  • OAuth 設定の有効化”: 有効(チェック)
  • “コールバックURL” :  http://localhost:5000/callback
  • “選択したOAuth範囲” : “一意のユーザー識別子にアクセス(openid)” , “ID URL サービスにアクセス(id, profile, email, phone)” を追加
  • “サポートされる認証フローに Proof Key for Code Exchange (PKCE) 拡張を要求” : チェックを外す(無効) 接続アプリケーションの作成画面

デジタルエクスペリエンスサイト作成

デジタルエクスペリエンスを有効化します。(「設定」-「デジタルエクスペリエンス」-「設定」)

新規サイトを作成します。テーマは何でも良いですが、ここでは “Aloha” を選んでおきます。

ワークスペースから「管理」-「メンバー」で、”External Identity User”プロファイルを追加します。「設定」で “状況” を有効化します。

ビルダーを開き、「公開」します。

テスト用ユーザ作成

まずは取引先責任者ページでカスタマーユーザーを有効化するボタンを表示させます。オブジェクトマネージャで取引先責任者のページレイアウトを変更します。”モバイルおよびLightningのアクション” から “カスタマーユーザーを有効化” を “Salesforce モバイルおよびLightning Exprienceのアクション” にドラッグ&ドロップします。

ページレイアウトで「カスタマーユーザーの有効化」ボタンを表示させる

任意の取引先責任者のレコードページから「カスタマーユーザーを有効化」ボタンをクリック

  • “メール” : ご自分のメールアドレス
  • “ユーザーライセンス” : External Identity
  • “プロファイル” : External Identity User

メールアドレスにパスワード変更のメールが届くのでパスワード変更をします。

Relying Partyアプリケーション

今回はPythonのFlaskを使って手軽に作っていきます。Python環境の構築はここでは触れません。必要なライブラリをインストールしておきます。

1$ pip install flask authlib requests python-dotenv

.env

次に環境変数を定義するファイルを作っておきます。コンシューマー鍵とコンシューマーの秘密は接続アプリケーションの詳細画面から「コンシューマーの詳細を管理」ボタンから確認できます。

1CLIENT_ID = '接続アプリケーションのコンシューマー鍵'
2CLIENT_SECRET = '接続アプリケーションのコンシューマーの秘密'
3OP_DOMAIN = 'デジタルエクスペリエンスサイトのURL(https://xxxx.develop.my.site.com/xx)'
4WELL_KNOWN_URL = ${OP_DOMAIN}/.well-known/openid-configuration

app.py

トップページのログインリンクからSalesforceで認証して、そのユーザの情報を表示するアプリケーションです。

OpenID Connectを処理するライブラリは Authlib を使っています。

1import os
2import urllib.request, urllib.parse
3import flask 
4from flask import session, redirect, render_template, request
5import json
6from authlib.integrations.flask_client import OAuth
7from authlib.jose import jwt, JsonWebKey
8from dotenv import load_dotenv
9
10#Init
11load_dotenv()
12
13app = flask.Flask(__name__)
14app.secret_key = os.urandom(32).hex()
15
16#環境変数呼び出し
17OP_DOMAIN = os.environ['OP_DOMAIN']
18CLIENT_ID = os.environ['CLIENT_ID']
19CLIENT_SECRET = os.environ['CLIENT_SECRET']
20WELL_KNOWN_URL = os.environ['WELL_KNOWN_URL']
21
22#OPのメタデータ取得
23def get_op_metadata():
24    response = urllib.request.urlopen(WELL_KNOWN_URL)
25    return json.loads(response.read())
26
27#OAuthインスタンス
28oauth = OAuth(app)
29metadata = get_op_metadata()
30oauth.register(
31    name='salesforce',
32    client_id=CLIENT_ID,
33    client_secret=CLIENT_SECRET,
34    client_kwrgs={
35        'scope': 'openid profile'
36    },
37    server_metadata_url=WELL_KNOWN_URL,
38)
39
40@app.route('/login')
41def login():
42    callback_url=flask.url_for('auth_callback', _external=True)
43    return oauth.salesforce.authorize_redirect(callback_url)
44
45
46@app.route('/callback')
47def auth_callback():
48    resp = oauth.salesforce.authorize_access_token()
49    if resp is None:
50        return 'Nothing data', 403
51    
52    # OPのJWKSを取得
53    key = urllib.request.urlopen(metadata['jwks_uri'])
54    jwks = JsonWebKey.import_key_set(json.loads(key.read()))
55    #IDトークンをデコードして検証
56    try:
57        claims = jwt.decode(resp['id_token'], jwks)
58        claims.validate()
59    except Exception as e:
60        print(e)
61        return 'Something wrong', 403
62    #クレームに対するチェック
63    if claims['iss'] != OP_DOMAIN:
64        return 'Invalid Issuer', 403
65    if claims['aud'] != CLIENT_ID:
66        return 'Invalid Audience', 403
67    
68    #Userinfoエンドポイントコール
69    apiHeaders = {
70        'Authorization': 'Bearer ' + resp["access_token"],
71        'Accept': 'application/json'
72    }
73    userInfoReq = urllib.request.Request(metadata['userinfo_endpoint'], headers=apiHeaders)
74    userInfoResp = urllib.request.urlopen(userInfoReq)
75    userinfo = json.loads(userInfoResp.read())
76    flask.session['profile'] = {
77        'id': userinfo['preferred_username'],
78        'name': userinfo['family_name'] + ' ' + userinfo['given_name'],
79        'picture': userinfo['picture'],
80        'email': userinfo['email'],
81    }
82
83    return flask.redirect(flask.url_for('index'))
84
85
86@app.route('/logout')
87def logout():
88    del flask.session['profile']
89
90    params = {'returnTo': flask.url_for('index', _external=True), 'client_id': CLIENT_ID}
91    return flask.redirect(metadata['end_session_endpoint'] + '?' + urllib.parse.urlencode(params))
92
93
94@app.route('/')
95def index():
96    if 'profile' in flask.session:
97        loggined = True
98        return render_template("index.html", loggined=loggined, userinfo=flask.session['profile'])
99    else:
100        loggined = False
101        return render_template("index.html", loggined=loggined)
102    
103    
104if __name__ == '__main__':
105    app.run()

templates/index.html

1{% block body %}
2  <h1 class="text-center">Customer Identity Demo</h1>
3  <div class="row">
4    <div class="col-sm-6 offset-sm-3">
5      <div class="jumbotron">
6        {% if loggined %}
7          <h2>あなたは、{{ userinfo['name'] }} さんですね。</h2><br>
8          <ul>
9            <li><img src={{ userinfo['picture'] }}></li>
10            <li>email: <a href="mailto:{{ userinfo['email'] }}">{{ userinfo['email'] }}</a></li>
11            <li>ユーザID: {{ userinfo['id'] }}</li>
12        </ul>
13          <a class="p-2 text-dark" href="/logout">Logout</a></br>
14        {% else %}
15          <a class="p-2 text-dark" href="/login">Log In / Register</a></br>
16        {% endif %}
17        Welcome to OpenID Connect RP Demo App! </br>
18      </div>
19    </div>
20  </div>
21{% endblock %}

動かしてみる

作ったFlaskプログラムを実行しましょう。

1$ python app.py

ブラウザにて http://localhost:5000 へアクセスします。

サンプルアプリのログイン前トップ画面

「Log In / Register」リンクを押すとSalesforceのExperience Cloudログイン画面にリダイレクトされます。

SalesforceのOpenID Connectログイン画面

作成したテスト用ユーザーでログインしてみましょう。Flaskアプリケーションに戻ってきてユーザー情報を表示します。

Salesforceでログインしたユーザーの情報を表示するSampleアプリ

まとめ

Salesforceは、IDaaS(Identity as a Service)として統合認証をSalesforce以外のアプリケーションに提供できます。CRMでSalesforceをご利用いただいているお客様が外部向けの統合認証を作る際の有力な候補の一つになるかと思います。

OpenID ConnectやOAuthに準拠していますので、一般的なライブラリを活用して効率的に実装できます。

次回はAPI経由で認証を利用するヘッドレスID APIをご紹介する予定です。