Documentation
¶
Overview ¶
Package client は OAuth 2.0 クライアントの登録情報と、用途別に方針を固定する Client Profile を提供する(docs/adr/0004-client-profiles.md)。
対応する仕様:
- RFC 6749 §2 Client Registration、§2.1 Client Types、§3.1.2 Redirection Endpoint、 §3.3 Access Token Scope
- RFC 7636 PKCE(Profile が S256 を必須にする)
- RFC 8252 §7.3 Loopback Interface Redirection(Native の port 無視照合)、 §8.3 Loopback Redirect Considerations(localhost ではなく loopback IP リテラルを使う)
- RFC 8707 §2 resource indicator(Client が要求できる resource の上限 Resources。MCP では必須)
- RFC 9700 §2.1.1 PKCE 必須、§4.1 redirect_uri の完全一致
- RFC 7591 §2 クライアントメタデータ(MetadataDocument のフィールド名)
- CIMD(draft-ietf-oauth-client-id-metadata-document-02)§3 Client Identifier URL(ParseClientIDURL)、 §4 文書の検証と client_id の一致、§4.1 共有シークレットの禁止、§4.2 redirect URI の完全一致(NewMCP)
- MCP Authorization Specification(2026-07-28)「Communication Security」(MCP の redirect URI は localhost か https)、「Client Registration」(CIMD の client_name は必須)
利用者が選べるのは Profile(Web / Native / Machine / MCP)と、Profile が開放する項目だけである。 grant_types、PKCE の有無、クライアント認証方式、redirect URI の照合規則は Profile が決める。 MCP Profile(docs/adr/0008-mcp-integration.md §4)は CIMD 文書から NewMCP で組み立て、 事前登録なしの Client を cimd.Resolver が解決する。
永続化の規約(docs/adr/0007-package-layout.md §2): Client はフィールドを公開した 構造体だが、構築は NewWeb / NewNative / NewMachine / NewMCP のみで行い、Adapter から復元した値は Validate で再検証する。
Index ¶
- Constants
- Variables
- func NewMachine(p MachineParams, now time.Time) (*Client, Secret, error)
- func NewWeb(p WebParams, now time.Time) (*Client, Secret, error)
- func ParseClientIDURL(id string) (*url.URL, error)
- func ValidScopeToken(s string) bool
- type AuthMethod
- type ChainResolver
- type Client
- func (c *Client) AcceptsAuthMethod(m AuthMethod) bool
- func (c *Client) Allows(gt GrantType) bool
- func (c *Client) AllowsResource(uri string) bool
- func (c *Client) DisallowedScopes(requested []string) []string
- func (c *Client) EffectiveAccessTokenTTL() time.Duration
- func (c *Client) IsPublic() bool
- func (c *Client) IssuesIDToken() bool
- func (c *Client) IssuesRefreshToken() bool
- func (c *Client) RequiresPKCE() bool
- func (c *Client) RequiresResource() bool
- func (c *Client) ResolveRedirectURI(requested string) (string, error)
- func (c *Client) Validate() error
- func (c *Client) VerifySecret(presented string) bool
- type GrantType
- type MachineParams
- type MetadataDocument
- type NativeParams
- type Profile
- type Resolver
- type Secret
- type Store
- type WebParams
Examples ¶
Constants ¶
const ( DefaultAccessTokenTTL = time.Hour MinAccessTokenTTL = time.Minute MaxAccessTokenTTL = 24 * time.Hour )
トークン有効期間の既定値と上限(ADR-0004 §2)。
const ( AlgRS256 = "RS256" AlgES256 = "ES256" )
ID Token の署名アルゴリズム(ADR-0002)。
const MinSecretLength = 32
MinSecretLength は利用者が指定するシークレットの最小長(バイト)。生成するシークレットは 256 bit 乱数の base64url(43 文字)なので、それに見合う長さを求める。
Variables ¶
var ( // ErrRedirectURIMismatch は、要求された redirect_uri が登録済みのどれとも一致しないことを表す。 // RFC 6749 §4.1.2.1 により、この場合は Client へリダイレクトしてはならない。 ErrRedirectURIMismatch = errors.New("client: redirect_uri does not match any registered URI") // ErrRedirectURIRequired は、redirect_uri が省略されたが登録 URI が 1 つに定まらないことを表す // (RFC 6749 §3.1.2.3)。 ErrRedirectURIRequired = errors.New("client: redirect_uri is required") )
var ErrInvalid = errors.New("client: invalid client")
ErrInvalid は Client の登録情報が Profile の規則に違反していることを表す。 具体的な違反内容は fmt.Errorf で付加される。
var ErrNotMetadataURL = errors.New("client: client_id is not a client id metadata document url")
ErrNotMetadataURL は client_id が CIMD の Client Identifier URL の形式(CIMD §3)でないことを表す。 cimd.Resolver はこのとき storage.ErrNotFound を返し、Store だけで解決した場合と区別しない。
var ErrUnresolvable = errors.New("client: client_id could not be resolved")
ErrUnresolvable は client_id に対応する Client を解決できなかったことを表す。
storage.ErrNotFound(登録がない)と違い、呼び出し側が error_description に出せる理由を持つ (CIMD 文書の取得失敗、文書の検証失敗、信頼ポリシーによる拒否)。プロトコル上の扱いは ErrNotFound と同じで、認可要求ではリダイレクトしないエラー、トークン要求では invalid_client になる。
Functions ¶
func NewMachine ¶
NewMachine は Machine to Machine Profile の confidential クライアントを作る。
Example ¶
ExampleNewMachine はユーザーを介さないサービス間通信(client_credentials)のクライアントを登録する。 Introspection を呼ぶ Resource Server もこの Profile で登録する(ADR-0006 §1)。
package main
import (
"fmt"
"log"
"time"
"github.com/u-masato/auth-engine/client"
)
// describe は Profile が固定する性質を 1 行にまとめる(ADR-0004)。
func describe(c *client.Client) string {
return fmt.Sprintf("profile=%s public=%t pkce=%t authorization_code=%t refresh_token=%t client_credentials=%t",
c.Profile, c.IsPublic(), c.RequiresPKCE(),
c.Allows(client.GrantAuthorizationCode), c.IssuesRefreshToken(), c.Allows(client.GrantClientCredentials))
}
func main() {
c, secret, err := client.NewMachine(client.MachineParams{
ID: "billing-batch",
Name: "Billing Batch",
Scopes: []string{"invoices.read"},
}, time.Now())
if err != nil {
log.Fatal(err)
}
fmt.Println(describe(c))
fmt.Println("secret issued:", len(secret) > 0)
}
Output: profile=machine public=false pkce=false authorization_code=false refresh_token=false client_credentials=true secret issued: true
func NewWeb ¶
NewWeb は Web Application Profile の confidential クライアントを作る。 生成したシークレットの平文は戻り値でのみ得られる。
Example ¶
ExampleNewWeb はサーバー側で秘密を保持できる Web アプリを登録する。 シークレットはここで 1 度だけ平文で返り、Store には SHA-256 ハッシュだけが残る。 呼び出し側で Client に渡し、ログには残さないこと。
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/u-masato/auth-engine/client"
"github.com/u-masato/auth-engine/storage/memory"
)
// describe は Profile が固定する性質を 1 行にまとめる(ADR-0004)。
func describe(c *client.Client) string {
return fmt.Sprintf("profile=%s public=%t pkce=%t authorization_code=%t refresh_token=%t client_credentials=%t",
c.Profile, c.IsPublic(), c.RequiresPKCE(),
c.Allows(client.GrantAuthorizationCode), c.IssuesRefreshToken(), c.Allows(client.GrantClientCredentials))
}
func main() {
c, secret, err := client.NewWeb(client.WebParams{
ID: "web-app",
Name: "Example Web App",
RedirectURIs: []string{"https://app.example.com/callback"}, // 完全一致で照合する
Scopes: []string{"openid", "profile", "email"},
}, time.Now())
if err != nil {
log.Fatal(err)
}
store := memory.New() // 本番では adapters/sqlite か adapters/postgres
if err := store.CreateClient(context.Background(), c); err != nil {
log.Fatal(err)
}
fmt.Println(describe(c))
fmt.Println("secret issued:", len(secret) > 0, "stored as hash:", c.SecretHash != "" && c.SecretHash != string(secret))
}
Output: profile=web public=false pkce=true authorization_code=true refresh_token=true client_credentials=false secret issued: true stored as hash: true
func ParseClientIDURL ¶
ParseClientIDURL は client_id が CIMD の Client Identifier URL(CIMD §3)かを判定し、解析結果を返す。
- https であること
- userinfo を含まないこと
- path を持つこと("/" だけは NOT RECOMMENDED だが受理する)
- "." / ".." のパスセグメントを含まないこと
- fragment を含まないこと
query は SHOULD NOT だが拒否しない。これらを満たさない値は ErrNotMetadataURL で、Store に登録された 通常の client_id として扱う。Store が発行する client_id は https:// で始めない(CIMD §7)。
func ValidScopeToken ¶
ValidScopeToken は s が RFC 6749 §3.3 の scope-token (1*( %x21 / %x23-5B / %x5D-7E )、つまり空白・二重引用符・バックスラッシュを含まない 印字可能 ASCII)であるかを返す。
Types ¶
type AuthMethod ¶
type AuthMethod string
AuthMethod はトークンエンドポイントでのクライアント認証方式 (OIDC Core §9 / RFC 7591 の token_endpoint_auth_method 値)。
const ( AuthClientSecretBasic AuthMethod = "client_secret_basic" AuthClientSecretPost AuthMethod = "client_secret_post" AuthNone AuthMethod = "none" )
type ChainResolver ¶
type ChainResolver []Resolver
ChainResolver は複数の Resolver を順に試す。先頭から storage.ErrNotFound 以外の結果が出るまで 進み、すべてが ErrNotFound なら ErrNotFound を返す(ADR-0008 §5: Store にあれば優先、なければ CIMD)。
type Client ¶
type Client struct {
ID string
Profile Profile
// 表示情報。Interaction App が同意画面で使う。
Name string
LogoURI string
PolicyURI string
// ClientURI はクライアントのホームページ(RFC 7591 §2 の client_uri)。CIMD 文書から組み立てた
// MCP Client だけが持ち、登録済み Client では空(ADR-0008 §6)。
ClientURI string
// RedirectURIs は登録済みの redirect URI。Machine では空。
RedirectURIs []string
// Scopes はこのクライアントが要求できる scope の上限。
Scopes []string
// Resources はこのクライアントが要求できる resource(RFC 8707)の上限(ADR-0004 §2、ADR-0008 §2)。
// 空なら Engine に設定された全 resource を要求できる。値は resource.Normalize の正規形で保持する。
Resources []string
// SecretHash はクライアントシークレットの SHA-256(16 進)。public クライアントでは空。
SecretHash string
// AccessTokenTTL は Access Token の有効期間。0 なら DefaultAccessTokenTTL。
AccessTokenTTL time.Duration
// IDTokenAlg は ID Token の署名アルゴリズム。Web / Native では RS256 か ES256、Machine では空。
IDTokenAlg string
// AllowInsecureLocalhost は開発用の緩和。Web では http://localhost と http loopback を、
// Native では http://localhost(RFC 8252 §8.3 で非推奨)を redirect URI として許可する。
AllowInsecureLocalhost bool
CreatedAt time.Time
}
Client は登録済み、または CIMD 文書から解決した OAuth 2.0 クライアント。
構築は NewWeb / NewNative / NewMachine / NewMCP で行う。フィールドは Adapter の直列化・復元の ために公開しているが、コア内では直接代入せず、復元後は Validate で検証する。
func NewMCP ¶
NewMCP は CIMD 文書から MCP Client Profile の public クライアントを組み立てる(ADR-0008 §4、§5)。
documentURL は文書を取得した URL。文書の client_id はこれと単純文字列比較で一致しなければならない (CIMD §4)。ネットワークには触れず、検証だけを行う。失敗は ErrInvalid。
Example ¶
ExampleNewMCP は CIMD(Client ID Metadata Document)から MCP クライアントを組み立てる。 通常は cimd.Resolver が文書を取得してこの関数を呼ぶので、組み込む側が直接呼ぶのは 文書を事前に Store に登録しておきたいとき(CIMD §7)に限られる。
package main
import (
"fmt"
"log"
"time"
"github.com/u-masato/auth-engine/client"
)
// describe は Profile が固定する性質を 1 行にまとめる(ADR-0004)。
func describe(c *client.Client) string {
return fmt.Sprintf("profile=%s public=%t pkce=%t authorization_code=%t refresh_token=%t client_credentials=%t",
c.Profile, c.IsPublic(), c.RequiresPKCE(),
c.Allows(client.GrantAuthorizationCode), c.IssuesRefreshToken(), c.Allows(client.GrantClientCredentials))
}
func main() {
const documentURL = "https://mcp-client.example/oauth/client.json"
c, err := client.NewMCP(client.MetadataDocument{
ClientID: documentURL, // 取得した URL と単純文字列比較で一致すること
ClientName: "Example MCP Client",
ClientURI: "https://mcp-client.example",
RedirectURIs: []string{"http://localhost:3000/callback"}, // MCP では http://localhost を許す
Scope: "files:read",
}, documentURL, time.Now())
if err != nil {
log.Fatal(err)
}
fmt.Println(describe(c))
fmt.Println("requires resource:", c.RequiresResource())
}
Output: profile=mcp public=true pkce=true authorization_code=true refresh_token=true client_credentials=false requires resource: true
func NewNative ¶
func NewNative(p NativeParams, now time.Time) (*Client, error)
NewNative は Native / CLI Profile の public クライアントを作る。
Example ¶
ExampleNewNative は CLI やモバイルアプリのような、秘密を持てない public クライアントを登録する。 loopback の redirect URI はポートを問わず照合する(RFC 8252 §7.3)。
package main
import (
"fmt"
"log"
"time"
"github.com/u-masato/auth-engine/client"
)
// describe は Profile が固定する性質を 1 行にまとめる(ADR-0004)。
func describe(c *client.Client) string {
return fmt.Sprintf("profile=%s public=%t pkce=%t authorization_code=%t refresh_token=%t client_credentials=%t",
c.Profile, c.IsPublic(), c.RequiresPKCE(),
c.Allows(client.GrantAuthorizationCode), c.IssuesRefreshToken(), c.Allows(client.GrantClientCredentials))
}
func main() {
c, err := client.NewNative(client.NativeParams{
ID: "cli",
Name: "Example CLI",
RedirectURIs: []string{"http://127.0.0.1/callback"},
Scopes: []string{"openid", "offline_access"},
}, time.Now())
if err != nil {
log.Fatal(err)
}
fmt.Println(describe(c))
}
Output: profile=native public=true pkce=true authorization_code=true refresh_token=true client_credentials=false
func (*Client) AcceptsAuthMethod ¶
func (c *Client) AcceptsAuthMethod(m AuthMethod) bool
AcceptsAuthMethod はトークンエンドポイントでの認証方式を受理するかを返す。
func (*Client) AllowsResource ¶
AllowsResource は uri(RFC 8707 の resource indicator)をこのクライアントに許可しているかを返す。 Resources が空なら全 resource を許可する(ADR-0008 §2)。設定済みかどうかの判定は resource.Set の責務で、ここでは Client の許可範囲だけを見る。
func (*Client) DisallowedScopes ¶
DisallowedScopes は requested のうち、このクライアントに許可されていない scope を返す。 空なら全て許可されている。Scopes を持たない MCP Client(CIMD 文書に scope がない)は 上限を持たず、要求 scope は resource ごとの上限(resource.Resource.Scopes)だけで絞られる。
func (*Client) EffectiveAccessTokenTTL ¶
EffectiveAccessTokenTTL は Access Token の有効期間(既定値を適用済み)を返す。
func (*Client) IssuesIDToken ¶
IssuesIDToken は openid scope 要求時に ID Token を発行できるかを返す。
func (*Client) IssuesRefreshToken ¶
IssuesRefreshToken は Refresh Token を発行するかを返す。
func (*Client) RequiresPKCE ¶
RequiresPKCE は Authorization Code Grant で PKCE(S256)を必須とするかを返す。
func (*Client) RequiresResource ¶
RequiresResource は認可要求に resource(RFC 8707)を必須とするかを返す(MCP Profile。ADR-0008 §4)。
func (*Client) ResolveRedirectURI ¶
ResolveRedirectURI は認可要求の redirect_uri を登録情報と照合し、実際に使う URI を返す。
- requested が空: 登録 URI がちょうど 1 つならそれを使う(RFC 6749 §3.1.2.3)。
- requested が非空: 完全一致を要求する(RFC 9700 §4.1.3)。Native と MCP の http loopback だけは ポート番号の違いを許す(RFC 8252 §7.3)。http://localhost はポートも含めて完全一致。
func (*Client) Validate ¶
Validate は Client が Profile の規則を満たすかを検証する。 コンストラクタが呼ぶほか、Adapter から復元した値の防御的検証にも使う。
func (*Client) VerifySecret ¶
VerifySecret は提示されたシークレットを定数時間で検証する。public クライアントでは常に false。
type MachineParams ¶
type MachineParams struct {
ID string
Name string
Secret string // 空なら生成(WebParams.Secret と同じ)
Scopes []string
Resources []string // 空なら制限なし
AccessTokenTTL time.Duration
}
MachineParams は NewMachine の入力。
type MetadataDocument ¶
type MetadataDocument struct {
// ClientID は文書の URL と単純文字列比較で一致しなければならない(CIMD §4)。
ClientID string `json:"client_id"`
// ClientName は同意画面に表示する名前。必須(MCP 仕様「Client Registration」)。
ClientName string `json:"client_name"`
ClientURI string `json:"client_uri,omitempty"`
LogoURI string `json:"logo_uri,omitempty"`
PolicyURI string `json:"policy_uri,omitempty"`
// RedirectURIs は必須。認可要求の redirect_uri はこのいずれかと完全一致すること(CIMD §4.2)。
RedirectURIs []string `json:"redirect_uris"`
// Scope は空白区切り。省略なら Client の scope 上限を持たない(resource ごとの上限だけが効く)。
Scope string `json:"scope,omitempty"`
// GrantTypes / ResponseTypes は Profile の固定値(authorization_code, refresh_token / code)と
// 矛盾しないことだけを確認する。Profile は変えない(ADR-0004 §5)。
GrantTypes []string `json:"grant_types,omitempty"`
ResponseTypes []string `json:"response_types,omitempty"`
// TokenEndpointAuthMethod は省略か none。private_key_jwt は M4 で受理する(CIMD §4.1、ADR-0008 §5)。
TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty"`
// 共有シークレットは CIMD 文書に置けない(CIMD §4.1)。存在すれば拒否するためだけに復号する。
ClientSecret string `json:"client_secret,omitempty"`
ClientSecretExpiresAt json.RawMessage `json:"client_secret_expires_at,omitempty"`
}
MetadataDocument は CIMD(Client ID Metadata Document)の内容。フィールド名は RFC 7591 §2 の クライアントメタデータに従う(CIMD §4)。cimd パッケージが取得して JSON を復号し、NewMCP が検証する。
ここに挙げないフィールド(jwks、jwks_uri、contacts、software_id 等)は無視する。
type NativeParams ¶
type NativeParams struct {
ID string
Name string
RedirectURIs []string
Scopes []string
Resources []string // 空なら制限なし
LogoURI string
PolicyURI string
AccessTokenTTL time.Duration
IDTokenAlg string
AllowInsecureLocalhost bool
}
NativeParams は NewNative の入力。
type Profile ¶
type Profile string
Profile はクライアントの用途を表し、RFC 6749 の個別設定を一括で決める。
const ( // ProfileWeb はサーバー側でシークレットを保持できる Web アプリケーション(confidential)。 ProfileWeb Profile = "web" // ProfileNative はモバイル・デスクトップ・CLI などシークレットを保持できないアプリ(public)。 ProfileNative Profile = "native" // ProfileMachine はユーザーを介さないサービス間通信(confidential、client_credentials のみ)。 ProfileMachine Profile = "machine" // ProfileMCP は MCP クライアント(public)。CIMD 文書(Client ID Metadata Document)の URL を // client_id とし、事前登録なしに接続する。resource(RFC 8707)が必須(ADR-0008 §4)。 ProfileMCP Profile = "mcp" )
type Resolver ¶
Resolver は client_id から Client を得る。
Store がそのまま Resolver となるほか、CIMD(Client ID Metadata Document)の URL 形式の client_id を 解決する cimd.Resolver がこのインターフェースを満たす。Engine は両者を ChainResolver で重ねる(ADR-0007 §4、ADR-0008 §5)。
不明な client_id には storage.ErrNotFound を返す。client_id の形式は正しいが解決に失敗した場合 (CIMD 文書の取得・検証失敗、信頼ポリシーによる拒否)は ErrUnresolvable を返す。
type Store ¶
type Store interface {
Resolver
// CreateClient は Client を保存する。同じ ID が既にあれば storage.ErrAlreadyExists を返す。
CreateClient(ctx context.Context, c *Client) error
}
Store は Client の永続化 Port。Adapter が実装する。
type WebParams ¶
type WebParams struct {
ID string // 空なら生成
Name string
// Secret は空なら生成する。既存システムからの移行や Conformance Suite のように
// 事前に決まったシークレットが必要なときだけ指定する。32 バイト以上であること。
Secret string
RedirectURIs []string
Scopes []string
Resources []string // 空なら制限なし
LogoURI string
PolicyURI string
AccessTokenTTL time.Duration // 0 なら既定
IDTokenAlg string // 空なら RS256
AllowInsecureLocalhost bool
}
WebParams は NewWeb の入力。