\n\n\n```\n\n### 試したこと・調べたこと\n- [x] teratailやGoogle等で検索した\n- [x] ソースコードを自分なりに変更した\n- [ ] 知人に聞いた\n- [ ] その他\n\n##### 上記の詳細・結果\nネットで検索していろいろ試してみましたが、うまくいきませんでした。\n\n<追記:20250806>\nconsoleを見たところ403エラーが出ているため、window.onload内の処理が行われていないことが判明しました。\n\n### 補足\n特になし","answerCount":2,"upvoteCount":0,"datePublished":"2025-08-05T02:31:04.888Z","dateModified":"2025-08-18T14:41:14.000Z","acceptedAnswer":{"@type":"Answer","text":"OAuth 2.0の認可コードフローを利用してコードを作成していましたが、\n認可コードフロー + PKCEを利用するコードに変更することで解決しました。\n(code_verifierとcode_challengeを利用する方法)\n\nコードは下記になります。\n元のコードにcode_verifierとcode_challengの処理を追加した感じになっています。\n結果的にクライアントシークレットは使用していません。(使用したらエラーが出ました)\n\nあと、Entra側の設定もいろいろ変更して試しましたので、うまくいった設定を記載しておきます。\n■Authentication\n ・webとSPAの設定:Single-Page Application\n ・黙示的な許可およびハイブリッドフロー:「アクセストークン」「IDトークン」ともに「オフ」\n ・パブリッククライアントフロー:有効\n\n```Javascript\n// Azure AD アプリケーションの情報を設定\nconst CLIENT_ID = \"XXXXX\"; // アプリケーション (クライアント) ID\nconst TENANT_ID = \"XXXXX\"; // テナントID \nconst REDIRECT_URI = \"http://localhost\"; // リダイレクトURI\n// const CLIENT_SECRET = \"XXXXX\"; // クライアントシークレット(本番環境ではここで代入させない)\nconst CODE_CHALLENGE = \"XXXXX\";\nconst CODE_CHALLENGE_METHOD = \"S256\";\nconst CODE_VERIFIER = \"XXXXX\";\nconst SCOPES = \"User.Read\"; // 必要なスコープ\n\n// エンドポイント\nconst AUTHORIZATION_ENDPOINT = `https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/authorize`;\nconst TOKEN_ENDPOINT = `https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token`;\n\n// DOM 要素の取得\nconst signInButton = document.getElementById(\"signInButton\");\nconst tokenDisplay = document.getElementById(\"tokenDisplay\");\n\n// サインインボタンのクリックイベント\nsignInButton.addEventListener(\"click\", () => {\n initiateLogin();\n});\n\n// ログインフローを開始\nfunction initiateLogin() {\n const state = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); // CSRF対策用のstate\n sessionStorage.setItem('oauth_state', state); // stateをセッションストレージに保存\n\n const authUrl = new URL(AUTHORIZATION_ENDPOINT);\n authUrl.searchParams.append(\"client_id\", CLIENT_ID);\n authUrl.searchParams.append(\"response_type\", \"code\"); // 認証コードフロー\n authUrl.searchParams.append(\"redirect_uri\", REDIRECT_URI);\n authUrl.searchParams.append(\"scope\", SCOPES);\n authUrl.searchParams.append(\"state\", state);\n authUrl.searchParams.append(\"code_challenge\", CODE_CHALLENGE);\n authUrl.searchParams.append(\"code_challenge_method\", CODE_CHALLENGE_METHOD);\n authUrl.searchParams.append(\"response_mode\", \"query\"); // クエリパラメータでコードを返す\n\n window.location.href = authUrl.toString(); // Azure AD の認証ページにリダイレクト\n}\n\n// ページロード時の処理\nwindow.onload = () => {\n const urlParams = new URLSearchParams(window.location.search);\n const code = urlParams.get(\"code\");\n const state = urlParams.get(\"state\");\n const error = urlParams.get(\"error\");\n const storedState = sessionStorage.getItem('oauth_state');\n\n if (error) {\n tokenDisplay.textContent = `認証エラー: ${error} - ${urlParams.get(\"error_description\")}`;\n console.error(\"認証エラー:\", error, urlParams.get(\"error_description\"));\n return;\n }\n\n if (code && state && state === storedState) {\n // 認証コードと state が存在し、state が一致する場合\n sessionStorage.removeItem('oauth_state'); // stateを削除\n getAccessToken(code);\n } else if (code && state && state !== storedState) {\n tokenDisplay.textContent = \"CSRF攻撃の可能性: stateが一致しません。\";\n console.error(\"State mismatch. Potential CSRF attack.\");\n } else {\n // 通常のページロード時、何も処理しない\n console.log(\"初期ページロードまたは認証コードなし\");\n }\n};\n\n// アクセストークンの取得\nasync function getAccessToken(code) {\n\n const params = new URLSearchParams();\n params.append(\"client_id\", CLIENT_ID);\n params.append(\"scope\", SCOPES);\n params.append(\"code\", code);\n params.append(\"redirect_uri\", REDIRECT_URI);\n params.append(\"grant_type\", \"authorization_code\");\n params.append(\"code_verifier\", CODE_VERIFIER);\n// params.append(\"client_secret\", CLIENT_SECRET); // クライアントシークレットを含める\n\n try {\n const response = await fetch(TOKEN_ENDPOINT, {\n method: \"POST\",\n mode: \"cors\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n });\n\n if (!response.ok) {\n const errorData = await response.json();\n throw new Error(`トークン取得エラー: ${response.status} - ${errorData.error_description || JSON.stringify(errorData)}`);\n }\n\n const data = await response.json();\n console.log(\"アクセストークン取得成功:\", data);\n tokenDisplay.textContent = `アクセストークン:\\n${data.access_token}\\n\\nIDトークン:\\n${data.id_token || \"なし\"}\\n\\nリフレッシュトークン:\\n${data.refresh_token || \"なし\"}`;\n\n // リフレッシュトークンが必要な場合は、ここで保存するなどして利用\n // (ただし、ブラウザのLocalStorage等に直接保存するのはセキュリティリスクがあるため注意)\n\n } catch (error) {\n console.error(\"トークン取得エラー:\", error);\n tokenDisplay.textContent = `トークン取得エラー: ${error.message}`;\n }\n}\n```","dateModified":"2025-08-18T14:37:46.000Z","datePublished":"2025-08-18T05:37:45.830Z","upvoteCount":0,"url":"https://teratail.com/questions/likupjuahf4pfl#reply-urr2z8bk7201at"},"suggestedAnswer":[{"@type":"Answer","text":"403エラーが返却されるのであればEntra側の設定に誤りはないでしょうか。\nググってみたりしてパッと出てきたものですが、以下は大丈夫でしょうか。\n・ログインしたユーザーがEntraアプリケーションへのアクセスが許可されているか\n Entraアプリケーションの「Authentication - サポートされているアカウントの種類」あたり?\n・スコープがEntraアプリケーションの「API のアクセス許可」に登録されているか\n・リダイレクトURLがEntraアプリケーションの「Authentication」のリダイレクトURLと一致しているか\n\nまた、consoleに表示された403エラーとは`console.error(\"認証エラー:\", error, urlParams.get(\"error_description\"));`によって表示されたものでしょうか。\nエラーメッセージの全てを知りたいです。","dateModified":"2025-08-06T03:29:07.967Z","datePublished":"2025-08-06T03:29:07.967Z","upvoteCount":0,"url":"https://teratail.com/questions/likupjuahf4pfl#reply-f4fmq2bctx4o2g","comment":[{"@type":"Comment","text":"ご回答ありがとうございます。\nエラーメッセージは“https://xxx.com//favicon.ico 403 (Forbidden)”です。","datePublished":"2025-08-06T03:52:45.902Z","dateModified":"2025-08-06T03:52:45.902Z"},{"@type":"Comment","text":"そのエラーメッセージ自体は今回の認証処理とは関係なさそうですね。\n\n以下のような状況であっていますか?\nあっている場合、https://xxx.com/ へリダイレクトする際のURLはどうなっていますか?ドメイン部を省略したURLすべてを知りたいです。\n・ブラウザのURLにhttps://xxx.com/ を入力し、アクセス\n・コンソールに\"初期ページロードまたは認証コードなし\"と表示される\n・サインインボタンをクリックする\n・Entraの認証ページにリダイレクトする\n・ログイン情報を入力する→成功\n・https://xxx.com/ にリダイレクトする\n・ここで.onloadの処理が実行されるが、stateやcode、errorなどのパラメータが取得できない","datePublished":"2025-08-06T04:20:42.261Z","dateModified":"2025-08-06T04:21:42.522Z"},{"@type":"Comment","text":"ご回答ありがとうございます。\n状況は記載していただいたとおりの動作です。\nネットでいろいろ調べたところ、リダイレクトURLの設定が間違っていることが分かりました。\nPCにXamppを入れて動作確認をしていますが、スキル不足でリダイレクトURLを実際のシステムのURLをhtmlとEntraに設定していました。\nそこで、htmlのリダイレクトURLを“http://localhost”に変更し、EntraのリダイレクトURLも“http://localhost”に変更してもらい動作させたところ、403エラーは出なくなりましたが“chrome-error://chromewebdata/”なるエラーが出るようになりました。\nブラウザはEdgeです。","datePublished":"2025-08-06T05:53:30.766Z","dateModified":"2025-08-06T05:53:30.766Z"},{"@type":"Comment","text":"<追記:20250807>\n“chrome-error://chromewebdata/”なるエラーはキャッシュクリアで解決しました。\n\n66行目の“getAccessToken”処理まで進むようになりましたが、実行すると\n“POST https://login.microsoftonline.com/XXXXXX/oauth2/v2.0/token 400 (Bad Request)”\nがconsoleに表示され、\n“トークン取得エラー: Error: トークン取得エラー: 400 - AADSTS9002326: Cross-origin token redemption is permitted only for the 'Single-Page Application' client-type. Request origin: 'http://localhost'. Trace ID: ~at getAccessToken”というエラーが出てしまいました。","datePublished":"2025-08-07T03:59:36.799Z","dateModified":"2025-08-07T03:59:36.799Z"},{"@type":"Comment","text":"CORSが許可されていないといったようなエラーのようですね。\nEntraの設定画面から以下の設定を行い、サインインするとどうなりますか?\nAuthentication (Preview) > 設定タブ > 「パブリック クライアント フローを許可する」を「有効」に変更する > 保存\n\n以下のドキュメントを参考にしました。\nフロントから直接認証するケースについてはあまり詳しくないのですが、要点は以下のように思いました。\n・Entraは機密クライアントとパブリッククライアントそれぞれに向けた認証方法を提供している\n・今回の仕組みはJSによって構築されており、シークレット情報を安全に保持できない\n →そのためパブリッククライアントとして扱われる\n・パブリッククライアントの場合は先の認証設定が必要となる\nhttps://learn.microsoft.com/en-us/entra/identity-platform/msal-client-applications#when-should-you-enable-allow-a-public-client-flow-in-your-app-registration\nhttps://learn.microsoft.com/ja-jp/azure/healthcare-apis/register-application","datePublished":"2025-08-08T01:52:07.443Z","dateModified":"2025-08-08T01:52:07.443Z"},{"@type":"Comment","text":"ご回答ありがとうございます。\nEntraの設定はシステム部に対応してもらっているのですが、\n今週はお盆休みで対応不可とのことで来週早々に試してみます。","datePublished":"2025-08-12T00:28:17.771Z","dateModified":"2025-08-12T00:28:17.771Z"},{"@type":"Comment","text":"<追記:20250812>\nご回答をいただく前にシングルページアプリケーションの設定が必要と思い、Entraで'Authentication'->'Single-Page Application')に'http://localhost'を追加してもらい試したところ、下記のエラーが出ました。\ninvalid_request AADSTS9002325: Proof Key for Code Exchange is required for cross-origin authorization code redemption.","datePublished":"2025-08-12T00:49:48.010Z","dateModified":"2025-08-12T00:49:48.010Z"},{"@type":"Comment","text":"システム部の方に“Authentication (Preview) > 設定タブ > 「パブリック クライアント フローを許可する」を「有効」に変更する > 保存”の設定を対応してもらえました。\n\n実行したところ、8/7の状況と同じになりました。\n“POST https://login.microsoftonline.com/XXXXXX/oauth2/v2.0/token 400 (Bad Request)”が表示される。\n“トークン取得エラー: Error: トークン取得エラー: 400 - AADSTS9002326: Cross-origin token redemption is permitted only for the 'Single-Page Application' client-type. Request origin: 'http://localhost'. Trace ID: ~at getAccessToken”というエラーが出る。\nといった状況です。","datePublished":"2025-08-13T02:43:46.143Z","dateModified":"2025-08-13T02:43:46.143Z"},{"@type":"Comment","text":"こちらの記事の解決方法はどうでしょうか。\nどちらの方法もリクエストヘッダーにはoriginパラメータを付与しましょうということのようです。\n\nhttps://teratail.com/questions/330746\nhttps://stackoverflow.com/questions/76438571/azure-api-aadsts9002326-cross-origin-token-redemption-is-permitted-only-for-t","datePublished":"2025-08-13T03:46:49.861Z","dateModified":"2025-08-13T03:46:49.861Z"},{"@type":"Comment","text":"ご回答ありがとうございます。\nmode: 'cors',を追加してみましたが、状況は同じでした。\n“POST https://login.microsoftonline.com/XXXXXX/oauth2/v2.0/token 400 (Bad Request)”","datePublished":"2025-08-13T06:17:56.141Z","dateModified":"2025-08-13T06:17:56.141Z"},{"@type":"Comment","text":"saborionさま\nいろいろご助言をいただき、ありがとうございました。\n参考にさせていただいたお陰で、別記のとおり無事解決することができました。\nお忙しいところお付き合いいただき申し訳ございませんでした。","datePublished":"2025-08-18T05:41:14.314Z","dateModified":"2025-08-18T05:41:14.314Z"}]}],"breadcrumb":{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"トップ","url":"https://teratail.com"},{"@type":"ListItem","position":2,"name":"JavaScriptに関する質問","url":"https://teratail.com/tags/JavaScript"},{"@type":"ListItem","position":3,"name":"JavaScript","url":"https://teratail.com/tags/JavaScript"}]}}}
質問するログイン新規登録
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Active Directory

Active Directoryは、 Windows Serverの機能の一つで、 マイクロソフトによって作られたディレクトリサービスです。 ネットワーク上に存在する様々なハードや利用者情報のアクセス権限などを一元管理が出来ます。

Azure

Azureは、マイクロソフトのクラウド プラットフォームで、旧称は Windows Azureです。PaaSとIaaSを組み合わせることで、 コンピューティング・ストレージ・データ・ネットワーキング・アプリケーションなど多くの機能を持ちます。

Q&A

解決済

2回答

463閲覧

JavascriptでMicrosoft Entra ID(旧Azure AD)認証を行いトークンを取得したい

poponta

総合スコア13

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Active Directory

Active Directoryは、 Windows Serverの機能の一つで、 マイクロソフトによって作られたディレクトリサービスです。 ネットワーク上に存在する様々なハードや利用者情報のアクセス権限などを一元管理が出来ます。

Azure

Azureは、マイクロソフトのクラウド プラットフォームで、旧称は Windows Azureです。PaaSとIaaSを組み合わせることで、 コンピューティング・ストレージ・データ・ネットワーキング・アプリケーションなど多くの機能を持ちます。

0グッド

0クリップ

投稿2025/08/05 02:31

編集2025/08/05 23:49

0

0

実現したいこと

JavascriptでMicrosoft Entra ID(旧Azure AD)認証を行いトークンを取得したいと考えております。

発生している問題・分からないこと

リダイレクトURIが表示されるところまでいきますが、URLに付加されているパラメータが取得できないため、その先の処理に進めません。ソースコード内の「urlParams = new URLSearchParams(window.location.search);」の処理がうまくいっていないようです。

該当のソースコード

Javascript

1// Azure AD アプリケーションの情報を設定 2const CLIENT_ID = "XXXXXX"; // アプリケーション (クライアント) ID 3const TENANT_ID = "XXXXXX"; // テナントID 4const REDIRECT_URI = "https://xxx.com/"; // リダイレクトURI 5const CLIENT_SECRET = "XXXXXX"; // クライアントシークレット(本番環境ではここで代入させない) 6 7const SCOPES = "User.Read"; // 必要なスコープ 8 9// エンドポイント 10const AUTHORIZATION_ENDPOINT = `https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/authorize`; 11const TOKEN_ENDPOINT = `https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token`; 12 13// DOM 要素の取得 14const signInButton = document.getElementById("signInButton"); 15const tokenDisplay = document.getElementById("tokenDisplay"); 16 17// サインインボタンのクリックイベント 18signInButton.addEventListener("click", () => { 19 initiateLogin(); 20}); 21 22// ログインフローを開始 23function initiateLogin() { 24 const state = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); // CSRF対策用のstate 25 sessionStorage.setItem('oauth_state', state); // stateをセッションストレージに保存 26 27 const authUrl = new URL(AUTHORIZATION_ENDPOINT); 28 authUrl.searchParams.append("client_id", CLIENT_ID); 29 authUrl.searchParams.append("response_type", "code"); // 認証コードフロー 30 authUrl.searchParams.append("redirect_uri", REDIRECT_URI); 31 authUrl.searchParams.append("scope", SCOPES); 32 authUrl.searchParams.append("state", state); 33 authUrl.searchParams.append("response_mode", "query"); // クエリパラメータでコードを返す 34 35 window.location.href = authUrl.toString(); // Azure AD の認証ページにリダイレクト 36} 37 38// ページロード時の処理 39window.onload = () => { 40 const urlParams = new URLSearchParams(window.location.search); 41 const code = urlParams.get("code"); 42 const state = urlParams.get("state"); 43 const error = urlParams.get("error"); 44 const storedState = sessionStorage.getItem('oauth_state'); 45 46 if (error) { 47 tokenDisplay.textContent = `認証エラー: ${error} - ${urlParams.get("error_description")}`; 48 console.error("認証エラー:", error, urlParams.get("error_description")); 49 return; 50 } 51 52 if (code && state && state === storedState) { 53 // 認証コードと state が存在し、state が一致する場合 54 sessionStorage.removeItem('oauth_state'); // stateを削除 55 getAccessToken(code); 56 } else if (code && state && state !== storedState) { 57 tokenDisplay.textContent = "CSRF攻撃の可能性: stateが一致しません。"; 58 console.error("State mismatch. Potential CSRF attack."); 59 } else { 60 // 通常のページロード時、何も処理しない 61 console.log("初期ページロードまたは認証コードなし"); 62 } 63}; 64 65// アクセストークンの取得 66async function getAccessToken(code) { 67 68 const params = new URLSearchParams(); 69 params.append("client_id", CLIENT_ID); 70 params.append("scope", SCOPES); 71 params.append("code", code); 72 params.append("redirect_uri", REDIRECT_URI); 73 params.append("grant_type", "authorization_code"); 74 params.append("client_secret", CLIENT_SECRET); // クライアントシークレットを含める 75 76 try { 77 const response = await fetch(TOKEN_ENDPOINT, { 78 method: "POST", 79 headers: { 80 "Content-Type": "application/x-www-form-urlencoded", 81 }, 82 body: params.toString(), 83 }); 84 85 if (!response.ok) { 86 const errorData = await response.json(); 87 throw new Error(`トークン取得エラー: ${response.status} - ${errorData.error_description || JSON.stringify(errorData)}`); 88 } 89 90 const data = await response.json(); 91 console.log("アクセストークン取得成功:", data); 92 tokenDisplay.textContent = `アクセストークン:\n${data.access_token}\n\nIDトークン:\n${data.id_token || "なし"}\n\nリフレッシュトークン:\n${data.refresh_token || "なし"}`; 93 94 // リフレッシュトークンが必要な場合は、ここで保存するなどして利用 95 // (ただし、ブラウザのLocalStorage等に直接保存するのはセキュリティリスクがあるため注意) 96 97 } catch (error) { 98 console.error("トークン取得エラー:", error); 99 tokenDisplay.textContent = `トークン取得エラー: ${error.message}`; 100 } 101}

html

1<!DOCTYPE html> 2<html> 3<head> 4 <title>Azure AD 認証 (MSALなし)</title> 5</head> 6<body> 7 <h1>Azure AD 認証サンプル (MSALなし)</h1> 8 <button id="signInButton">サインイン</button> 9 <pre id="tokenDisplay"></pre> 10 11 <script src="app.js"></script> 12</body> 13</html>

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

ネットで検索していろいろ試してみましたが、うまくいきませんでした。

<追記:20250806>
consoleを見たところ403エラーが出ているため、window.onload内の処理が行われていないことが判明しました。

補足

特になし

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

自己解決

OAuth 2.0の認可コードフローを利用してコードを作成していましたが、
認可コードフロー + PKCEを利用するコードに変更することで解決しました。
(code_verifierとcode_challengeを利用する方法)

コードは下記になります。
元のコードにcode_verifierとcode_challengの処理を追加した感じになっています。
結果的にクライアントシークレットは使用していません。(使用したらエラーが出ました)

あと、Entra側の設定もいろいろ変更して試しましたので、うまくいった設定を記載しておきます。
■Authentication
・webとSPAの設定:Single-Page Application
・黙示的な許可およびハイブリッドフロー:「アクセストークン」「IDトークン」ともに「オフ」
・パブリッククライアントフロー:有効

Javascript

1// Azure AD アプリケーションの情報を設定 2const CLIENT_ID = "XXXXX"; // アプリケーション (クライアント) ID 3const TENANT_ID = "XXXXX"; // テナントID 4const REDIRECT_URI = "http://localhost"; // リダイレクトURI 5// const CLIENT_SECRET = "XXXXX"; // クライアントシークレット(本番環境ではここで代入させない) 6const CODE_CHALLENGE = "XXXXX"; 7const CODE_CHALLENGE_METHOD = "S256"; 8const CODE_VERIFIER = "XXXXX"; 9const SCOPES = "User.Read"; // 必要なスコープ 10 11// エンドポイント 12const AUTHORIZATION_ENDPOINT = `https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/authorize`; 13const TOKEN_ENDPOINT = `https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token`; 14 15// DOM 要素の取得 16const signInButton = document.getElementById("signInButton"); 17const tokenDisplay = document.getElementById("tokenDisplay"); 18 19// サインインボタンのクリックイベント 20signInButton.addEventListener("click", () => { 21 initiateLogin(); 22}); 23 24// ログインフローを開始 25function initiateLogin() { 26 const state = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); // CSRF対策用のstate 27 sessionStorage.setItem('oauth_state', state); // stateをセッションストレージに保存 28 29 const authUrl = new URL(AUTHORIZATION_ENDPOINT); 30 authUrl.searchParams.append("client_id", CLIENT_ID); 31 authUrl.searchParams.append("response_type", "code"); // 認証コードフロー 32 authUrl.searchParams.append("redirect_uri", REDIRECT_URI); 33 authUrl.searchParams.append("scope", SCOPES); 34 authUrl.searchParams.append("state", state); 35 authUrl.searchParams.append("code_challenge", CODE_CHALLENGE); 36 authUrl.searchParams.append("code_challenge_method", CODE_CHALLENGE_METHOD); 37 authUrl.searchParams.append("response_mode", "query"); // クエリパラメータでコードを返す 38 39 window.location.href = authUrl.toString(); // Azure AD の認証ページにリダイレクト 40} 41 42// ページロード時の処理 43window.onload = () => { 44 const urlParams = new URLSearchParams(window.location.search); 45 const code = urlParams.get("code"); 46 const state = urlParams.get("state"); 47 const error = urlParams.get("error"); 48 const storedState = sessionStorage.getItem('oauth_state'); 49 50 if (error) { 51 tokenDisplay.textContent = `認証エラー: ${error} - ${urlParams.get("error_description")}`; 52 console.error("認証エラー:", error, urlParams.get("error_description")); 53 return; 54 } 55 56 if (code && state && state === storedState) { 57 // 認証コードと state が存在し、state が一致する場合 58 sessionStorage.removeItem('oauth_state'); // stateを削除 59 getAccessToken(code); 60 } else if (code && state && state !== storedState) { 61 tokenDisplay.textContent = "CSRF攻撃の可能性: stateが一致しません。"; 62 console.error("State mismatch. Potential CSRF attack."); 63 } else { 64 // 通常のページロード時、何も処理しない 65 console.log("初期ページロードまたは認証コードなし"); 66 } 67}; 68 69// アクセストークンの取得 70async function getAccessToken(code) { 71 72 const params = new URLSearchParams(); 73 params.append("client_id", CLIENT_ID); 74 params.append("scope", SCOPES); 75 params.append("code", code); 76 params.append("redirect_uri", REDIRECT_URI); 77 params.append("grant_type", "authorization_code"); 78 params.append("code_verifier", CODE_VERIFIER); 79// params.append("client_secret", CLIENT_SECRET); // クライアントシークレットを含める 80 81 try { 82 const response = await fetch(TOKEN_ENDPOINT, { 83 method: "POST", 84 mode: "cors", 85 headers: { 86 "Content-Type": "application/x-www-form-urlencoded", 87 }, 88 body: params.toString(), 89 }); 90 91 if (!response.ok) { 92 const errorData = await response.json(); 93 throw new Error(`トークン取得エラー: ${response.status} - ${errorData.error_description || JSON.stringify(errorData)}`); 94 } 95 96 const data = await response.json(); 97 console.log("アクセストークン取得成功:", data); 98 tokenDisplay.textContent = `アクセストークン:\n${data.access_token}\n\nIDトークン:\n${data.id_token || "なし"}\n\nリフレッシュトークン:\n${data.refresh_token || "なし"}`; 99 100 // リフレッシュトークンが必要な場合は、ここで保存するなどして利用 101 // (ただし、ブラウザのLocalStorage等に直接保存するのはセキュリティリスクがあるため注意) 102 103 } catch (error) { 104 console.error("トークン取得エラー:", error); 105 tokenDisplay.textContent = `トークン取得エラー: ${error.message}`; 106 } 107}

投稿2025/08/18 05:37

poponta

総合スコア13

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

403エラーが返却されるのであればEntra側の設定に誤りはないでしょうか。
ググってみたりしてパッと出てきたものですが、以下は大丈夫でしょうか。
・ログインしたユーザーがEntraアプリケーションへのアクセスが許可されているか
Entraアプリケーションの「Authentication - サポートされているアカウントの種類」あたり?
・スコープがEntraアプリケーションの「API のアクセス許可」に登録されているか
・リダイレクトURLがEntraアプリケーションの「Authentication」のリダイレクトURLと一致しているか

また、consoleに表示された403エラーとはconsole.error("認証エラー:", error, urlParams.get("error_description"));によって表示されたものでしょうか。
エラーメッセージの全てを知りたいです。

投稿2025/08/06 03:29

saborion

総合スコア4

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

poponta

2025/08/06 03:52

ご回答ありがとうございます。 エラーメッセージは“https://xxx.com//favicon.ico 403 (Forbidden)”です。
saborion

2025/08/06 04:21 編集

そのエラーメッセージ自体は今回の認証処理とは関係なさそうですね。 以下のような状況であっていますか? あっている場合、https://xxx.com/ へリダイレクトする際のURLはどうなっていますか?ドメイン部を省略したURLすべてを知りたいです。 ・ブラウザのURLにhttps://xxx.com/ を入力し、アクセス ・コンソールに"初期ページロードまたは認証コードなし"と表示される ・サインインボタンをクリックする ・Entraの認証ページにリダイレクトする ・ログイン情報を入力する→成功 ・https://xxx.com/ にリダイレクトする ・ここで.onloadの処理が実行されるが、stateやcode、errorなどのパラメータが取得できない
poponta

2025/08/06 05:53

ご回答ありがとうございます。 状況は記載していただいたとおりの動作です。 ネットでいろいろ調べたところ、リダイレクトURLの設定が間違っていることが分かりました。 PCにXamppを入れて動作確認をしていますが、スキル不足でリダイレクトURLを実際のシステムのURLをhtmlとEntraに設定していました。 そこで、htmlのリダイレクトURLを“http://localhost”に変更し、EntraのリダイレクトURLも“http://localhost”に変更してもらい動作させたところ、403エラーは出なくなりましたが“chrome-error://chromewebdata/”なるエラーが出るようになりました。 ブラウザはEdgeです。
poponta

2025/08/07 03:59

<追記:20250807> “chrome-error://chromewebdata/”なるエラーはキャッシュクリアで解決しました。 66行目の“getAccessToken”処理まで進むようになりましたが、実行すると “POST https://login.microsoftonline.com/XXXXXX/oauth2/v2.0/token 400 (Bad Request)” がconsoleに表示され、 “トークン取得エラー: Error: トークン取得エラー: 400 - AADSTS9002326: Cross-origin token redemption is permitted only for the 'Single-Page Application' client-type. Request origin: 'http://localhost'. Trace ID: ~at getAccessToken”というエラーが出てしまいました。
saborion

2025/08/08 01:52

CORSが許可されていないといったようなエラーのようですね。 Entraの設定画面から以下の設定を行い、サインインするとどうなりますか? Authentication (Preview) > 設定タブ > 「パブリック クライアント フローを許可する」を「有効」に変更する > 保存 以下のドキュメントを参考にしました。 フロントから直接認証するケースについてはあまり詳しくないのですが、要点は以下のように思いました。 ・Entraは機密クライアントとパブリッククライアントそれぞれに向けた認証方法を提供している ・今回の仕組みはJSによって構築されており、シークレット情報を安全に保持できない  →そのためパブリッククライアントとして扱われる ・パブリッククライアントの場合は先の認証設定が必要となる https://learn.microsoft.com/en-us/entra/identity-platform/msal-client-applications#when-should-you-enable-allow-a-public-client-flow-in-your-app-registration https://learn.microsoft.com/ja-jp/azure/healthcare-apis/register-application
poponta

2025/08/12 00:28

ご回答ありがとうございます。 Entraの設定はシステム部に対応してもらっているのですが、 今週はお盆休みで対応不可とのことで来週早々に試してみます。
poponta

2025/08/12 00:49

<追記:20250812> ご回答をいただく前にシングルページアプリケーションの設定が必要と思い、Entraで'Authentication'->'Single-Page Application')に'http://localhost'を追加してもらい試したところ、下記のエラーが出ました。 invalid_request AADSTS9002325: Proof Key for Code Exchange is required for cross-origin authorization code redemption.
poponta

2025/08/13 02:43

システム部の方に“Authentication (Preview) > 設定タブ > 「パブリック クライアント フローを許可する」を「有効」に変更する > 保存”の設定を対応してもらえました。 実行したところ、8/7の状況と同じになりました。 “POST https://login.microsoftonline.com/XXXXXX/oauth2/v2.0/token 400 (Bad Request)”が表示される。 “トークン取得エラー: Error: トークン取得エラー: 400 - AADSTS9002326: Cross-origin token redemption is permitted only for the 'Single-Page Application' client-type. Request origin: 'http://localhost'. Trace ID: ~at getAccessToken”というエラーが出る。 といった状況です。
poponta

2025/08/18 05:41

saborionさま いろいろご助言をいただき、ありがとうございました。 参考にさせていただいたお陰で、別記のとおり無事解決することができました。 お忙しいところお付き合いいただき申し訳ございませんでした。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.29%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問