NAVER WORKS(LINE WORKS) IdPモードでNext.js App RouterベースのWebアプリケーションにSAML 2.0 SSOを連携する実践ベストプラクティスガイド。
このガイドは分量が多いため、4つのドキュメントに分かれている。
SAML (Security Assertion Markup Language) 2.0は、異なるドメイン間で認証・認可情報を安全に交換するためのXMLベースの標準プロトコルである。SSO(Single Sign-On)を実現する最も広く使われるエンタープライズ標準の一つだ。
NAVER WORKS(旧LINE WORKS)は韓国の企業向けメッセンジャー及びコラボレーションプラットフォームで、IdP(Identity Provider)モードをサポートしている。これを活用すると:
NAVER WORKS IdP ←→ Next.js App Router (SP)
├── SAML 認証フロー
├── セッション管理 (JWE)
└── アクセス制御
| 用語 | 説明 | 例 |
|---|---|---|
| IdP (Identity Provider) | ユーザー認証を担当するシステム | NAVER WORKS |
| SP (Service Provider) | 認証を委任するアプリケーション | Next.js Webアプリ |
| Assertion | ユーザー情報を含むXMLドキュメント | NameID, SessionIndex等 |
| ACS (Assertion Consumer Service) | SPでSAML Responseを受け取るエンドポイント | /auth/callback |
| Entity ID (Issuer) | IdP/SP各自の一意識別子 | https://your-domain.com |
| Binding | SAMLメッセージの伝達方式 | HTTP-POST, HTTP-Redirect |
NAVER WORKS IdPモードで最も一般的なパターンはSP-Initiated SSOだ。ユーザーがSP(我々のアプリ)に先にアクセスすると、SPがIdPに認証を要求する方式である。
<saml2p:AuthnRequest
xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol"
AssertionConsumerServiceURL="https://your-domain.com/auth/callback"
ID="{一意ID}"
IssueInstant="2025-01-15T07:42:35Z"
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Version="2.0">
<saml2:Issuer
xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">
https://your-domain.com
</saml2:Issuer>
<saml2p:NameIDPolicy
Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"/>
</saml2p:AuthnRequest>
<saml2p:Response Destination="https://your-domain.com/auth/callback"
ID="{NAVER WORKS発行ID}"
InResponseTo="{AuthnRequest ID}"
IssueInstant="2025-01-15T07:42:27.614Z" Version="2.0">
<saml2:Issuer>https://auth.worksmobile.com/saml2/{domain}</saml2:Issuer>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">...</Signature>
<saml2p:Status>
<saml2p:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</saml2p:Status>
<saml2:Assertion ID="{ID}" IssueInstant="..." Version="2.0">
<saml2:Subject>
<saml2:NameID Format="unspecified">user@company.com</saml2:NameID>
<saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml2:SubjectConfirmationData
InResponseTo="{AuthnRequest ID}"
NotOnOrAfter="{有効期限}"
Recipient="https://your-domain.com/auth/callback"/>
</saml2:SubjectConfirmation>
</saml2:Subject>
<saml2:Conditions NotBefore="{開始}" NotOnOrAfter="{終了}">
<saml2:AudienceRestriction>
<saml2:Audience>https://your-domain.com</saml2:Audience>
</saml2:AudienceRestriction>
</saml2:Conditions>
<saml2:AuthnStatement AuthnInstant="..." SessionIndex="{セッションID}">
<saml2:AuthnContext>
<saml2:AuthnContextClassRef>
urn:oasis:names:tc:SAML:2.0:ac:classes:Password
</saml2:AuthnContextClassRef>
</saml2:AuthnContext>
</saml2:AuthnStatement>
</saml2:Assertion>
</saml2p:Response>
NAVER WORKSは標準SAML 2.0に準拠しているが、他のIdP(Google Workspace, Okta, Azure AD等)と比較していくつかの固有の特性がある。これを事前に把握しないと、連携過程で予期しないエラーに遭遇する。
| 項目 | 一般的なIdP (Google, Okta等) | NAVER WORKS |
|---|---|---|
| Signature Reference URI | URI="#responseId" (特定ノード) |
URI="" (ドキュメント全体) |
| 署名位置 | ResponseまたはAssertion個別 | Responseレベルのみ |
| Assertion署名 | 個別署名含む | 不含 |
| SignatureMethod | SHA-256 (rsa-sha256) |
SHA-1 (rsa-sha1) |
| CanonicalizationMethod | xml-c14n-20010315 |
xml-c14n-20010315#WithComments |
| NameID Format | 多様 (email, persistent等) | unspecified 固定 |
| NameID値 | 設定により異なる | ユーザーのNAVER WORKS ID (メール) |
| Protocol Binding | 主にHTTP-POST | HTTP-POST + HTTP-Redirect両方使用 |
核心的な注意: 空のSignature Reference URI (
URI="")は最大の差別点であり、最も多くの問題を引き起こす特性である。XML Digital Signature仕様(RFC 3275)でURI=""は完全に有効だが、多くのSAMLライブラリがこれを処理できない。詳細はトラブルシューティングドキュメントを参照。
NAVER WORKSはAuthnRequestのProtocolBinding設定により応答方式が異なる:
ACSエンドポイントは必ずGETとPOST両方を処理しなければならない。
{
"dependencies": {
"@node-saml/node-saml": "^5.1.0",
"xml-crypto": "^6.1.2",
"@xmldom/xmldom": "^0.8.11",
"jose": "^6.1.3"
}
}
| パッケージ | 役割 | 備考 |
|---|---|---|
@node-saml/node-saml |
SAMLプロトコル処理 (1次) | AuthnRequest生成、Response検証 |
xml-crypto |
XML電子署名検証 (Fallback) | URI=""サポート |
@xmldom/xmldom |
XML DOMパース | Fallbackパスで使用 |
jose |
JWEトークン生成/検証 | セッション管理 |
node-samlはすでにxml-cryptoと@xmldom/xmldomに依存しているが、pnpmのstrict依存性ポリシーのため、他のパッケージの内部依存を直接importできない。Fallback検証パスでこれらのパッケージを直接使用するため、dependenciesに明示的に追加する必要がある。
pnpm add xml-crypto @xmldom/xmldom
npmやyarnを使用する場合、ホイスティングにより別途インストールなしでimport可能な場合があるが、明示的に追加するのが安全である。
src/
├── lib/auth/
│ ├── config.ts # 環境変数ベースの設定 (SAML, セッション, アクセス制御)
│ ├── saml.ts # SAMLプロトコル (AuthnRequest, Response検証)
│ └── session.ts # JWEセッション管理 (生成, 読取, 削除, 更新)
├── app/(auth)/
│ └── auth/
│ ├── login/page.tsx # SSOエントリポイント (Server Component)
│ ├── callback/route.ts # ACSエンドポイント (GET + POST)
│ ├── logout/route.ts # ログアウト + SLO
│ └── error/page.tsx # 認証エラーページ
└── app/(protected)/
└── layout.tsx # 認証ゲート (セッション確認)
RouteハンドラーはHTTPリクエスト/レスポンスのみを担当し、認証ロジックはlib/auth/に委任する。この分離のおかげで:
session.tsのみ修正