Help us shape the future of Go! Take the 2026 Go Developer Survey

client

package
v0.0.0-...-e29b97a Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 25, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

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

Examples

Constants

View Source
const (
	DefaultAccessTokenTTL = time.Hour
	MinAccessTokenTTL     = time.Minute
	MaxAccessTokenTTL     = 24 * time.Hour
)

トークン有効期間の既定値と上限(ADR-0004 §2)。

View Source
const (
	AlgRS256 = "RS256"
	AlgES256 = "ES256"
)

ID Token の署名アルゴリズム(ADR-0002)。

View Source
const MinSecretLength = 32

MinSecretLength は利用者が指定するシークレットの最小長(バイト)。生成するシークレットは 256 bit 乱数の base64url(43 文字)なので、それに見合う長さを求める。

Variables

View Source
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")
)
View Source
var ErrInvalid = errors.New("client: invalid client")

ErrInvalid は Client の登録情報が Profile の規則に違反していることを表す。 具体的な違反内容は fmt.Errorf で付加される。

View Source
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 だけで解決した場合と区別しない。

View Source
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

func NewMachine(p MachineParams, now time.Time) (*Client, Secret, error)

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

func NewWeb(p WebParams, now time.Time) (*Client, Secret, error)

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

func ParseClientIDURL(id string) (*url.URL, error)

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

func ValidScopeToken(s string) bool

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)。

func (ChainResolver) GetClient

func (r ChainResolver) GetClient(ctx context.Context, id string) (*Client, error)

GetClient は各 Resolver を順に呼ぶ。

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

func NewMCP(doc MetadataDocument, documentURL string, now time.Time) (*Client, error)

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) Allows

func (c *Client) Allows(gt GrantType) bool

Allows は grant_type をこのクライアントに許可しているかを返す。

func (*Client) AllowsResource

func (c *Client) AllowsResource(uri string) bool

AllowsResource は uri(RFC 8707 の resource indicator)をこのクライアントに許可しているかを返す。 Resources が空なら全 resource を許可する(ADR-0008 §2)。設定済みかどうかの判定は resource.Set の責務で、ここでは Client の許可範囲だけを見る。

func (*Client) DisallowedScopes

func (c *Client) DisallowedScopes(requested []string) []string

DisallowedScopes は requested のうち、このクライアントに許可されていない scope を返す。 空なら全て許可されている。Scopes を持たない MCP Client(CIMD 文書に scope がない)は 上限を持たず、要求 scope は resource ごとの上限(resource.Resource.Scopes)だけで絞られる。

func (*Client) EffectiveAccessTokenTTL

func (c *Client) EffectiveAccessTokenTTL() time.Duration

EffectiveAccessTokenTTL は Access Token の有効期間(既定値を適用済み)を返す。

func (*Client) IsPublic

func (c *Client) IsPublic() bool

IsPublic は RFC 6749 §2.1 の public クライアントかを返す。

func (*Client) IssuesIDToken

func (c *Client) IssuesIDToken() bool

IssuesIDToken は openid scope 要求時に ID Token を発行できるかを返す。

func (*Client) IssuesRefreshToken

func (c *Client) IssuesRefreshToken() bool

IssuesRefreshToken は Refresh Token を発行するかを返す。

func (*Client) RequiresPKCE

func (c *Client) RequiresPKCE() bool

RequiresPKCE は Authorization Code Grant で PKCE(S256)を必須とするかを返す。

func (*Client) RequiresResource

func (c *Client) RequiresResource() bool

RequiresResource は認可要求に resource(RFC 8707)を必須とするかを返す(MCP Profile。ADR-0008 §4)。

func (*Client) ResolveRedirectURI

func (c *Client) ResolveRedirectURI(requested string) (string, error)

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

func (c *Client) Validate() error

Validate は Client が Profile の規則を満たすかを検証する。 コンストラクタが呼ぶほか、Adapter から復元した値の防御的検証にも使う。

func (*Client) VerifySecret

func (c *Client) VerifySecret(presented string) bool

VerifySecret は提示されたシークレットを定数時間で検証する。public クライアントでは常に false。

type GrantType

type GrantType string

GrantType は RFC 6749 §4 / §6 の grant_type パラメータ値。

const (
	GrantAuthorizationCode GrantType = "authorization_code"
	GrantRefreshToken      GrantType = "refresh_token"
	GrantClientCredentials GrantType = "client_credentials"
)

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"
)

func (Profile) Valid

func (p Profile) Valid() bool

Valid は既知の Profile かを返す。

type Resolver

type Resolver interface {
	GetClient(ctx context.Context, id string) (*Client, error)
}

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 Secret

type Secret string

Secret はクライアントシークレットの平文。生成時に 1 回だけ返し、保存はハッシュのみ。

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 の入力。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL