- Accordion
- Alert
- Alert Dialog
- Aspect Ratio
- Attachment
- Avatar
- Badge
- Breadcrumb
- Bubble
- Button
- Button Group
- Calendar
- Card
- Carousel
- Chart
- Checkbox
- Collapsible
- Combobox
- Command
- Context Menu
- Data Table
- Date Picker
- Dialog
- Direction
- Drawer
- Dropdown Menu
- Empty
- Field
- Hover Card
- Input
- Input Group
- Input OTP
- Item
- Kbd
- Label
- Marker
- Menubar
- Message
- Message Scroller
- Native Select
- Navigation Menu
- Pagination
- Popover
- Progress
- Questionnaire
- Radio Group
- Resizable
- Scroll Area
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Spinner
- Switch
- Table
- Tabs
- Textarea
- Toast
- Toggle
- Toggle Group
- Tooltip
- Typography
認証を利用すると、プライベートレジストリを運用したり、コンポーネントへのアクセスを制御したり、チームやユーザーごとに異なるコンテンツを提供したりできます。このガイドでは、一般的な認証パターンとその設定方法を紹介します。
認証によって、以下のユースケースが実現できます。
- プライベートコンポーネント: ビジネスロジックや社内コンポーネントを安全に保ちます
- チーム固有のリソース: チームごとに異なるコンポーネントを提供します
- アクセス制御: 機密性の高いコンポーネントや実験的なコンポーネントを閲覧できる人を制限します
- 利用状況の分析: 組織内で誰がどのコンポーネントを使っているかを把握します
- ライセンス管理: プレミアムまたはライセンス付きコンポーネントの配布先を制御します
一般的な認証パターン#
トークンベースの認証#
最も一般的な方法は、Bearer トークンや API キーを使用する方法です。
{
"registries": {
"@private": {
"url": "https://registry.company.com/{name}.json",
"headers": {
"Authorization": "Bearer ${REGISTRY_TOKEN}"
}
}
}
}トークンは環境変数に設定します。
REGISTRY_TOKEN=your_secret_token_hereAPI キー認証#
レジストリによっては、ヘッダーで API キーを使用します。
{
"registries": {
"@company": {
"url": "https://api.company.com/registry/{name}.json",
"headers": {
"X-API-Key": "${API_KEY}",
"X-Workspace-Id": "${WORKSPACE_ID}"
}
}
}
}クエリパラメーター認証#
よりシンプルな構成では、クエリパラメーターを使用します。
{
"registries": {
"@internal": {
"url": "https://registry.company.com/{name}.json",
"params": {
"token": "${ACCESS_TOKEN}"
}
}
}
}この設定では、次の URL が生成されます。https://registry.company.com/button.json?token=your_token
サーバー側の実装#
レジストリサーバーに認証を追加する方法は次のとおりです。
Next.js API ルートの例#
import { NextRequest, NextResponse } from "next/server"
export async function GET(
request: NextRequest,
{ params }: { params: { name: string } }
) {
// Get token from Authorization header.
const authHeader = request.headers.get("authorization")
const token = authHeader?.replace("Bearer ", "")
// Or from query parameters.
const queryToken = request.nextUrl.searchParams.get("token")
// Check if token is valid.
if (!isValidToken(token || queryToken)) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}
// Check if token can access this component.
if (!hasAccessToComponent(token, params.name)) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 })
}
// Return the component.
const component = await getComponent(params.name)
return NextResponse.json(component)
}
function isValidToken(token: string | null) {
// Add your token validation logic here.
// Check against database, JWT validation, etc.
return token === process.env.VALID_TOKEN
}
function hasAccessToComponent(token: string, componentName: string) {
// Add role-based access control here.
// Check if token can access specific component.
return true // Your logic here.
}Express.js の例#
app.get("/registry/:name.json", (req, res) => {
const token = req.headers.authorization?.replace("Bearer ", "")
if (!isValidToken(token)) {
return res.status(401).json({ error: "Unauthorized" })
}
const component = getComponent(req.params.name)
if (!component) {
return res.status(404).json({ error: "Component not found" })
}
res.json(component)
})高度な認証パターン#
チームベースのアクセス#
チームごとに異なるコンポーネントを提供します。
async function GET(request: NextRequest) {
const token = extractToken(request)
const team = await getTeamFromToken(token)
// Get components for this team.
const components = await getComponentsForTeam(team)
return NextResponse.json(components)
}ユーザーごとにパーソナライズされたレジストリ#
ユーザーの設定に基づいてコンポーネントを提供します。
async function GET(request: NextRequest) {
const user = await authenticateUser(request)
// Get user's style and framework preferences.
const preferences = await getUserPreferences(user.id)
// Get personalized component version.
const component = await getPersonalizedComponent(params.name, preferences)
return NextResponse.json(component)
}一時アクセストークン#
有効期限付きのトークンを使うと、セキュリティが向上します。
interface TemporaryToken {
token: string
expiresAt: Date
scope: string[]
}
async function validateTemporaryToken(token: string) {
const tokenData = await getTokenData(token)
if (!tokenData) return false
if (new Date() > tokenData.expiresAt) return false
return true
}マルチレジストリ認証#
名前空間付きレジストリを使うと、認証方法の異なる複数のレジストリを設定できます。
{
"registries": {
"@public": "https://public.company.com/{name}.json",
"@internal": {
"url": "https://internal.company.com/{name}.json",
"headers": {
"Authorization": "Bearer ${INTERNAL_TOKEN}"
}
},
"@premium": {
"url": "https://premium.company.com/{name}.json",
"headers": {
"X-License-Key": "${LICENSE_KEY}"
}
}
}
}この構成では、次のことが可能です。
- パブリックレジストリとプライベートレジストリを混在させます
- レジストリごとに異なる認証を使用します
- アクセスレベルごとにコンポーネントを整理します
セキュリティのベストプラクティス#
環境変数を使用する#
トークンをバージョン管理にコミットしないでください。必ず環境変数を使用します。
REGISTRY_TOKEN=your_secret_token_here
API_KEY=your_api_key_herecomponents.json では、次のように参照します。
{
"registries": {
"@private": {
"url": "https://registry.company.com/{name}.json",
"headers": {
"Authorization": "Bearer ${REGISTRY_TOKEN}"
}
}
}
}HTTPS を使用する#
転送中のトークンを保護するため、レジストリには必ず HTTPS の URL を使用します。
{
"@secure": "https://registry.company.com/{name}.json" // ✅
"@insecure": "http://registry.company.com/{name}.json" // ❌
}レート制限を追加する#
レジストリを不正利用から保護します。
import rateLimit from "express-rate-limit"
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
})
app.use("/registry", limiter)トークンをローテーションする#
アクセストークンは定期的に変更します。
// Create new token with expiration.
function generateToken() {
const token = crypto.randomBytes(32).toString("hex")
const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) // 30 days.
return { token, expiresAt }
}アクセスをログに記録する#
セキュリティと分析のために、レジストリへのアクセスを記録します。
async function logAccess(request: Request, component: string, userId: string) {
await db.accessLog.create({
timestamp: new Date(),
userId,
component,
ip: request.ip,
userAgent: request.headers["user-agent"],
})
}認証のテスト#
認証付きレジストリをローカルでテストします。
# Test with curl.
curl -H "Authorization: Bearer your_token" \
https://registry.company.com/button.json
# Test with the CLI.
REGISTRY_TOKEN=your_token npx shadcn@latest add @private/buttonエラー処理#
shadcn CLI は認証エラーを適切に処理します。
- 401 Unauthorized: トークンが無効か、不足しています
- 403 Forbidden: このリソースに対するトークンの権限が不足しています
- 429 Too Many Requests: レート制限を超過しました
カスタムエラーメッセージ#
レジストリサーバーはレスポンスボディにカスタムエラーメッセージを返すことができ、CLI はそれをユーザーに表示します。
// Registry server returns custom error
return NextResponse.json(
{
error: "Unauthorized",
message:
"Your subscription has expired. Please renew at company.com/billing",
},
{ status: 403 }
)ユーザーには次のように表示されます。
Your subscription has expired. Please renew at company.com/billingこの仕組みを使うと、状況に応じたガイダンスを提供できます。
// Different error messages for different scenarios
if (!token) {
return NextResponse.json(
{
error: "Unauthorized",
message:
"Authentication required. Set REGISTRY_TOKEN in your .env.local file",
},
{ status: 401 }
)
}
if (isExpiredToken(token)) {
return NextResponse.json(
{
error: "Unauthorized",
message: "Token expired. Request a new token at company.com/tokens",
},
{ status: 401 }
)
}
if (!hasTeamAccess(token, component)) {
return NextResponse.json(
{
error: "Forbidden",
message: `Component '${component}' is restricted to the Design team`,
},
{ status: 403 }
)
}次のステップ#
複数のレジストリでの認証設定や高度なパターンを利用するには、名前空間付きレジストリのドキュメントを参照してください。以下の内容が解説されています。
- 複数の認証付きレジストリを設定する方法
- 名前空間ごとに異なる認証を使用する方法
- レジストリをまたいだ依存関係の解決
- 高度な認証パターン