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

token

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: 16 Imported by: 0

Documentation

Overview

Package token は Access Token / Refresh Token の発行・交換・Rotation と、 それらをまとめる Token Family を扱う。

Token Family は Code 交換 1 回ごとに作られ、Refresh Token の Rotation はその中で連鎖する。 Rotation 済みの Refresh Token が再提示されたら Family 全体を失効させる (RFC 9700 §4.14.2)。Code が再利用されたら、その Code から作られた Family を失効させる (RFC 6749 §4.1.2)。

Access Token は既定で Opaque(ADR-0006 §1)。平文は発行時にのみ返し、保存は SHA-256 の Hash のみ。 認可要求の resource(RFC 8707)は Family の Audience として引き継ぎ、トークン要求の resource で その部分集合に絞った Audience を Access Token に付ける(ADR-0008 §3)。

発行は Issuer(issuer.go)が担う。形式は Audience の resource 設定で決まり、Opaque(OpaqueIssuer)か RFC 9068 の JWT(JWTIssuer、jwt.go)。JWT でも Store に行を持ち、Introspection と Revocation は同じ経路。

Refresh Token は Family 作成からの絶対期限(既定 30 日)と最終使用からのスライド期限 (既定 14 日)を持つ。Rotation のたびに後継のスライド期限は延びるが絶対期限は超えない。

ファイル構成: service.go(Exchange の入口と authorization_code)、refresh.go(refresh_token)、 clientcredentials.go(client_credentials)、clientauth.go(クライアント認証)、issuer.go / jwt.go (Access Token の発行)、introspect.go / revoke.go(Introspection と Revocation)。

対応する仕様:

  • RFC 6749 §2.3 Client Authentication、§4.1.3 Access Token Request、 §4.1.4 Access Token Response、§4.4 Client Credentials Grant、§5.1 Successful Response、 §5.2 Error Response、§6 Refreshing an Access Token
  • RFC 6750 §4 Bearer Token の返却形式
  • RFC 7636 §4.5 code_verifier の送信、§4.6 サーバー側の検証
  • RFC 8707 §2.2 トークン要求の resource パラメータ(認可済みの範囲内で Audience を絞る)
  • RFC 7662 §2.2 Introspection 応答の aud
  • RFC 9068 §2 JWT Access Token(typ: at+jwt、§2.2 の Claims、§3 aud は resource indicator)
  • RFC 9700 §4.14 Refresh Token Protection(Rotation と再利用検知)
  • OIDC Core §12 Using Refresh Tokens(Refresh 応答の ID Token)

Index

Examples

Constants

View Source
const (
	CodeInvalidRequest       = "invalid_request"
	CodeInvalidClient        = "invalid_client"
	CodeInvalidGrant         = "invalid_grant"
	CodeUnauthorizedClient   = "unauthorized_client"
	CodeUnsupportedGrantType = "unsupported_grant_type"
	CodeInvalidScope         = "invalid_scope"

	// CodeInvalidTarget は resource パラメータが不正、または認可されていない Resource Server を
	// 指している(RFC 8707 §2.2)。
	CodeInvalidTarget = "invalid_target"
)

RFC 6749 §5.2 のエラーコード。

View Source
const (
	HintAccessToken  = "access_token"
	HintRefreshToken = "refresh_token"
)

token_type_hint の値(RFC 7009 §2.1、RFC 7662 §2.1)。

View Source
const (
	DefaultRefreshTokenTTL     = 30 * 24 * time.Hour
	DefaultRefreshTokenIdleTTL = 14 * 24 * time.Hour
)

Refresh Token の有効期間の既定値(ADR-0006 §4)。

  • 絶対期限: Family 作成からの寿命。Rotation しても延びない。
  • スライド期限: 最終使用からの寿命。Rotation のたびに延びるが、絶対期限を超えない。
View Source
const (
	AccessTokenPrefix  = "at_"
	RefreshTokenPrefix = "rt_"
)

トークン平文の接頭辞。ログやシークレットスキャナでの識別用。

View Source
const JWTAccessTokenType = "at+jwt"

JWTAccessTokenType は JWT Access Token の typ ヘッダ(RFC 9068 §2.1)。 Resource Server はこれ以外(ID Token の "JWT" など)を拒否する(§4)。

Variables

View Source
var ErrUnsupportedFormat = errors.New("token: no issuer for the requested access token format")

ErrUnsupportedFormat は、要求された形式を発行できる Issuer が配線されていない(設定不備。500)。

Functions

This section is empty.

Types

type AccessStatus

type AccessStatus string

AccessStatus は Access Token の状態。

const (
	AccessActive  AccessStatus = "active"
	AccessRevoked AccessStatus = "revoked"
)

type AccessToken

type AccessToken struct {
	Hash     string
	FamilyID string
	GrantID  string
	ClientID string
	Subject  string
	Scopes   []string
	// Audience はこのトークンが向けられる resource(RFC 8707 §2.2)。Family.Audience の部分集合。
	// Introspection の aud に載せる。空なら認可サーバー自身(UserInfo)向け。
	Audience []string

	Status    AccessStatus
	IssuedAt  time.Time
	ExpiresAt time.Time
	RevokedAt time.Time // ゼロ値なら未失効
}

AccessToken は Opaque な Access Token の記録。平文は保存しない。

func (*AccessToken) Expired

func (t *AccessToken) Expired(now time.Time) bool

Expired は now 時点で期限切れかを返す。ExpiresAt ちょうどは期限切れとする。

func (*AccessToken) Valid

func (t *AccessToken) Valid(now time.Time) bool

Valid は now 時点で有効(未失効・未期限)かを返す。Grant / Family の状態は含まない。

type ByFormat

type ByFormat map[resource.Format]Issuer

ByFormat は形式ごとの Issuer に振り分ける。Engine が Opaque と JWT の 2 実装を配線する。

func (ByFormat) Issue

func (b ByFormat) Issue(ctx context.Context, in IssueInput) (string, *AccessToken, error)

Issue は in.Format(空なら FormatOpaque)の Issuer に委譲する。なければ ErrUnsupportedFormat。

type Credentials

type Credentials struct {
	ClientID string
	Secret   string
	// Method は資格情報の提示方法。Secret が無ければ client.AuthNone。
	Method client.AuthMethod
}

Credentials はトークンエンドポイントで提示されたクライアントの資格情報(RFC 6749 §2.3)。

HTTP 層が Authorization ヘッダ(client_secret_basic)またはフォーム(client_secret_post、 public クライアントの client_id のみ)から取り出して渡す。複数の方式が同時に使われた場合は HTTP 層が invalid_request にする(RFC 6749 §2.3: MUST NOT use more than one method)。

type Error

type Error struct {
	Code        string
	Description string
}

Error はトークンエンドポイントのプロトコルエラー(RFC 6749 §5.2)。 内部障害(Store の失敗など)は *Error ではなく通常の error で返し、HTTP 層が 500 にする。

func (*Error) Error

func (e *Error) Error() string

func (*Error) HTTPStatus

func (e *Error) HTTPStatus() int

HTTPStatus は RFC 6749 §5.2 に従う HTTP ステータス。invalid_client は 401、他は 400。

type Family

type Family struct {
	ID       string
	GrantID  string
	ClientID string
	Subject  string
	// Scopes はこの Family のトークンに付く scope。Grant の scope の部分集合。
	Scopes []string
	// Audience はこの Family のトークンが向けられる resource(RFC 8707)の上限。
	// 認可要求の resource(Code の Resource)をそのまま引き継ぐ。Refresh で発行する Access Token の
	// Audience はこの部分集合(ADR-0008 §3)。resource なしの要求では空。
	Audience []string

	// AuthenticatedAt / AMR / ACR は元の認証の情報。Refresh 時に発行する ID Token の
	// auth_time / amr / acr は元の認証を表さなければならない(OIDC Core §12.2)ため、
	// Family に写しを持つ。client_credentials の Family ではゼロ値。
	AuthenticatedAt time.Time
	AMR             []string
	ACR             string

	Status    FamilyStatus
	CreatedAt time.Time
	// ExpiresAt は Refresh Token の絶対期限(Family 全体の寿命)。Refresh Token を
	// 発行しない Family ではゼロ値。
	ExpiresAt time.Time
	RevokedAt time.Time // ゼロ値なら未失効
}

Family は 1 回の Code 交換(または client_credentials 要求)から派生するトークンの集まり。

func (*Family) Active

func (f *Family) Active() bool

Active は失効していないかを返す。

type FamilyStatus

type FamilyStatus string

FamilyStatus は Token Family の状態。

const (
	FamilyActive  FamilyStatus = "active"
	FamilyRevoked FamilyStatus = "revoked"
)

type IDTokenInput

type IDTokenInput struct {
	Subject         string
	AuthenticatedAt time.Time
	Nonce           string
	AMR             []string
	ACR             string
	// AccessToken は同時に発行した Access Token の平文(at_hash 用)。
	AccessToken string
	Scopes      []string
}

IDTokenInput は ID Token の発行に必要な、トークンエンドポイントが持つ情報。

type IDTokenIssuer

type IDTokenIssuer interface {
	IssueIDToken(ctx context.Context, c *client.Client, in IDTokenInput) (string, error)
}

IDTokenIssuer は ID Token を発行する Port。oidc パッケージが実装し、Engine が配線する。 nil のままなら openid scope が付与されても id_token は返さない(OAuth 2.0 のみの構成)。

type Introspection

type Introspection struct {
	Active bool
	// TokenType は Access Token なら "Bearer"。Refresh Token は RFC 6749 §5.1 の token_type を
	// 持たないので空。
	TokenType string
	Scopes    []string
	// Audience はトークンが向けられる resource(RFC 7662 §2.2 の aud)。Access Token はそのトークンの、
	// Refresh Token は Family の Audience。空なら認可サーバー自身向け。
	Audience  []string
	ClientID  string
	Subject   string
	IssuedAt  time.Time
	ExpiresAt time.Time
	// Kind は内部の種別(HintAccessToken / HintRefreshToken)。HTTP 応答には載せない。
	Kind string
}

Introspection はトークンの状態(RFC 7662 §2.2)。Active が false なら他のフィールドは空。

type IntrospectionRequest

type IntrospectionRequest struct {
	Token         string
	TokenTypeHint string
	Credentials   Credentials
}

IntrospectionRequest は Introspection エンドポイントの要求(RFC 7662 §2.1)。

type IssueInput

type IssueInput struct {
	Client *client.Client
	// Family はトークンが属する Token Family。GrantID・Subject と、認可コード由来の認証情報
	// (AuthenticatedAt / AMR / ACR。client_credentials ではゼロ値)をここから取る。
	Family *Family
	// Scopes は Family.Scopes の、Audience は Family.Audience の部分集合(要求時の縮小を反映済み)。
	Scopes   []string
	Audience []string
	// Format は発行する形式。Audience の resource 設定で決まり、Audience が空なら FormatOpaque(ADR-0008 §3)。
	Format resource.Format
	// Alg は FormatJWT のときの署名アルゴリズム(resource.Resource.EffectiveAlg)。
	Alg string
	Now time.Time
}

IssueInput は Access Token の発行に必要な情報。Service が Code 交換・Refresh・client_credentials のそれぞれで組み立てる。

type Issuer

type Issuer interface {
	Issue(ctx context.Context, in IssueInput) (plaintext string, rec *AccessToken, err error)
}

Issuer は Access Token を発行する拡張点(ADR-0007 §4、ADR-0008 §3)。

戻り値の平文は応答にのみ使い、rec は Store に保存する記録。形式に依らず rec.Hash は 平文全体の SHA-256 で、Introspection と Revocation は同じ経路で動く。

type JWTIssuer

type JWTIssuer struct {
	// Issuer は iss クレーム(RFC 8414 の issuer と同じ値)。
	Issuer string
	Keys   *keys.Set
}

JWTIssuer は RFC 9068 の JWT Access Token を発行する Issuer。

署名は keys の RS256 / ES256(ADR-0002。JOSE の利用は keys に閉じる)。JWT でも Store に AccessToken の行を持ち(Hash は JWT 文字列の SHA-256)、Introspection と Revocation は Opaque と 同じ経路で動く。ただし Resource Server が JWKS でローカル検証する限り、失効は exp まで伝播しない (ADR-0006 §1、ADR-0008 §3)。JWT を選ぶ resource は短い AccessTokenTTL の Client と組み合わせること。

Example

ExampleJWTIssuer は、resource ごとに Access Token の形式を JWT(RFC 9068)にする。 JWTIssuer は Engine が配線するので、利用者は Config.Resources の AccessTokenFormat を選ぶだけでよい。 Resource Server は JWKS の鍵でローカルに検証できる。ただし失効は exp まで伝わらないので、 JWT を選ぶ resource には AccessTokenTTL の短い Client を組み合わせる(ADR-0008 §3)。

package main

import (
	"context"
	"fmt"
	"log"
	"log/slog"
	"time"

	authengine "github.com/u-masato/auth-engine"
	"github.com/u-masato/auth-engine/client"
	"github.com/u-masato/auth-engine/keys"
	"github.com/u-masato/auth-engine/resource"
	"github.com/u-masato/auth-engine/storage/memory"
	"github.com/u-masato/auth-engine/token"
)

func main() {
	ctx := context.Background()
	now := time.Now()
	store := memory.New()
	svc, secret, err := client.NewMachine(client.MachineParams{
		ID: "svc", Name: "Service", Scopes: []string{"files.read"}, AccessTokenTTL: 5 * time.Minute,
	}, now)
	if err != nil {
		log.Fatal(err)
	}
	if err := store.CreateClient(ctx, svc); err != nil {
		log.Fatal(err)
	}
	ks := keys.NewSet()
	k, err := keys.Generate(keys.AlgRS256, now)
	if err != nil {
		log.Fatal(err)
	}
	if err := ks.AddSigning(k); err != nil {
		log.Fatal(err)
	}
	engine, err := authengine.New(authengine.Config{
		Issuer: "https://as.example.com", Store: store, Keys: ks, Logger: slog.New(slog.DiscardHandler),
		Resources: []resource.Resource{{URI: "https://files.example.com", AccessTokenFormat: resource.FormatJWT}},
	})
	if err != nil {
		log.Fatal(err)
	}
	res, err := engine.Token.Exchange(ctx, token.Request{
		GrantType:   "client_credentials",
		Credentials: token.Credentials{ClientID: "svc", Secret: string(secret), Method: client.AuthClientSecretBasic},
		Resource:    []string{"https://files.example.com"},
	})
	if err != nil {
		log.Fatal(err)
	}

	// Resource Server 側の検証。実際には /jwks から得た公開鍵で行い、
	// typ・iss・aud・exp を確かめる(RFC 9068 §4)。
	var claims struct {
		Iss      string   `json:"iss"`
		Aud      []string `json:"aud"`
		ClientID string   `json:"client_id"`
		Scope    string   `json:"scope"`
	}
	h, err := engine.Keys.Verify(res.AccessToken, &claims)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(h.Type, h.Alg, claims.Iss, claims.Aud, claims.ClientID, claims.Scope, res.ExpiresIn)

	// Opaque と同じく Store に記録されるので、Introspection と失効も使える。
	info, err := engine.Token.Inspect(ctx, res.AccessToken, token.HintAccessToken)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(info.Active, info.Audience)

}
Output:
at+jwt RS256 https://as.example.com [https://files.example.com] svc files.read 300
true [https://files.example.com]

func (*JWTIssuer) Issue

Issue は in.Alg で署名した JWT を平文として返す。aud は in.Audience で、空なら発行しない (JWT は resource 向けにだけ使う。ADR-0008 §3)。

type OpaqueIssuer

type OpaqueIssuer struct{}

OpaqueIssuer はランダム文字列の Access Token を発行する既定の Issuer(ADR-0006 §1)。

func (OpaqueIssuer) Issue

Issue は 256 bit 乱数の平文(接頭辞 at_)と、その SHA-256 を Hash に持つ記録を返す。

type RefreshStatus

type RefreshStatus string

RefreshStatus は Refresh Token の状態。

const (
	RefreshActive  RefreshStatus = "active"
	RefreshRotated RefreshStatus = "rotated"
	RefreshRevoked RefreshStatus = "revoked"
)

type RefreshToken

type RefreshToken struct {
	Hash     string
	FamilyID string

	Status         RefreshStatus
	ReplacedByHash string // Status が rotated のとき、後継トークンの Hash
	IssuedAt       time.Time
	ExpiresAt      time.Time
	RotatedAt      time.Time // ゼロ値なら未 Rotation
	RevokedAt      time.Time // ゼロ値なら未失効
}

RefreshToken は Refresh Token の記録。Rotation で rotated になり、後継の Hash を持つ。

func (*RefreshToken) Expired

func (t *RefreshToken) Expired(now time.Time) bool

Expired は now 時点で期限切れかを返す。ExpiresAt ちょうどは期限切れとする。

type Request

type Request struct {
	GrantType   string
	Credentials Credentials

	// authorization_code
	Code         string
	RedirectURI  string
	CodeVerifier string

	// refresh_token
	RefreshToken string

	// Scope は refresh_token / client_credentials の scope パラメータ(空白区切り、未指定なら空)。
	Scope string

	// Resource は resource パラメータ(RFC 8707 §2.2。複数可)。発行する Access Token の Audience を
	// 認可済みの範囲内で絞る。未指定なら認可済みの全部。
	Resource []string
}

Request はトークン要求(RFC 6749 §4.1.3、§4.4.2、§6)。HTTP 層がフォームから組み立てる。

type Response

type Response struct {
	AccessToken  string `json:"access_token"`
	TokenType    string `json:"token_type"`
	ExpiresIn    int64  `json:"expires_in"`
	Scope        string `json:"scope,omitempty"`
	RefreshToken string `json:"refresh_token,omitempty"`
	IDToken      string `json:"id_token,omitempty"`
}

Response はトークン応答(RFC 6749 §5.1、RFC 6750)。

type RevocationRequest

type RevocationRequest struct {
	Token         string
	TokenTypeHint string
	Credentials   Credentials
}

RevocationRequest は Revocation エンドポイントの要求(RFC 7009 §2.1)。

type Service

type Service struct {
	Clients client.Resolver
	Store   Store
	// Grants は client_credentials が Grant を記録するのに使う。
	Grants grant.Store
	Clock  clock.Clock
	// Logger は監査イベント(発行、再利用検知)の出力先。nil なら slog.Default()。
	Logger *slog.Logger
	// IDTokens は openid scope が付与されたときに ID Token を発行する。nil なら発行しない。
	IDTokens IDTokenIssuer
	// Resources は resource パラメータ(RFC 8707 §2.2)で指定できる Resource Server の集合。
	// authorization.Service と同じ Set を Engine が配線する。nil なら空集合。
	Resources *resource.Set
	// Issuer は Access Token を発行する(ADR-0007 §4)。Engine は形式ごとの ByFormat(Opaque と JWT)を
	// 配線する。nil なら OpaqueIssuer だけで、JWT を要求する resource は内部エラーになる。
	Issuer Issuer

	// RefreshTokenTTL は Refresh Token の絶対期限、RefreshTokenIdleTTL はスライド期限。
	// 0 なら既定値(DefaultRefreshTokenTTL / DefaultRefreshTokenIdleTTL)。
	RefreshTokenTTL     time.Duration
	RefreshTokenIdleTTL time.Duration
}

Service はトークンエンドポイントのユースケース群。フィールドは Engine が配線する。

func (*Service) Exchange

func (s *Service) Exchange(ctx context.Context, req Request) (*Response, error)

Exchange はトークン要求を処理する。失敗は *Error(プロトコルエラー)か、内部障害の error。

func (*Service) Inspect

func (s *Service) Inspect(ctx context.Context, plaintext, hint string) (*Introspection, error)

Inspect はトークン平文の状態を返す(Go API。ADR-0006 §5 の Inspect)。

不明・期限切れ・失効(Grant / Family の失効を含む)はすべて Active: false で、エラーにしない。 hint は探索順序のヒントで、外れていても両方の種別を探す。

Example

ExampleService_Inspect は Go API でトークンの状態を調べる。Resource Server をエンジンと同じプロセスに 置くなら、HTTP の Introspection(RFC 7662)を経由せずにこれで検証できる。 不明・期限切れ・失効はすべて Active: false で返り、エラーにはならない。

package main

import (
	"context"
	"fmt"
	"log"
	"log/slog"
	"time"

	authengine "github.com/u-masato/auth-engine"
	"github.com/u-masato/auth-engine/client"
	"github.com/u-masato/auth-engine/keys"
	"github.com/u-masato/auth-engine/storage/memory"
	"github.com/u-masato/auth-engine/token"
)

// exampleEngine は Machine クライアント svc を登録した Engine と、svc に client_credentials で
// 発行した Access Token を返す。
func exampleEngine(ctx context.Context) (*authengine.Engine, string) {
	now := time.Now()
	store := memory.New()
	svc, secret, err := client.NewMachine(client.MachineParams{ID: "svc", Name: "Service", Scopes: []string{"api.read"}}, now)
	if err != nil {
		log.Fatal(err)
	}
	if err := store.CreateClient(ctx, svc); err != nil {
		log.Fatal(err)
	}
	ks := keys.NewSet()
	k, err := keys.Generate(keys.AlgRS256, now)
	if err != nil {
		log.Fatal(err)
	}
	if err := ks.AddSigning(k); err != nil {
		log.Fatal(err)
	}
	engine, err := authengine.New(authengine.Config{Issuer: "https://as.example.com", Store: store, Keys: ks, Logger: slog.New(slog.DiscardHandler)})
	if err != nil {
		log.Fatal(err)
	}
	res, err := engine.Token.Exchange(ctx, token.Request{
		GrantType:   "client_credentials",
		Credentials: token.Credentials{ClientID: "svc", Secret: string(secret), Method: client.AuthClientSecretBasic},
	})
	if err != nil {
		log.Fatal(err)
	}
	return engine, res.AccessToken
}

func main() {
	ctx := context.Background()
	engine, accessToken := exampleEngine(ctx)

	info, err := engine.Token.Inspect(ctx, accessToken, token.HintAccessToken)
	if err != nil {
		log.Fatal(err) // 内部障害だけがエラーになる
	}
	fmt.Println(info.Active, info.TokenType, info.ClientID, info.Scopes)

	info, err = engine.Token.Inspect(ctx, "at_unknown", "")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(info.Active)

}
Output:
true Bearer svc [api.read]
false

func (*Service) Introspect

func (s *Service) Introspect(ctx context.Context, req IntrospectionRequest) (*Introspection, error)

Introspect は Introspection エンドポイントの処理。

呼び出し元は confidential な Client(Web / Machine Profile)として認証しなければならない。 Resource Server は Machine Profile の Client として登録する想定(ADR-0006 §1)。 public な Client は自分を証明できないので invalid_client。認証に通れば、他 Client に 発行されたトークンも参照できる(Resource Server は任意の Client のトークンを検証する)。

func (*Service) Revoke

func (s *Service) Revoke(ctx context.Context, req RevocationRequest) error

Revoke は Revocation エンドポイントの処理。

Client を認証し、そのトークンが提示した Client に発行されたものであれば失効させる。 他 Client のトークン・不明なトークン・失効済みのトークンに対しては何もせず成功を返す (RFC 7009 §2.2: invalid token は成功。他 Client のトークンについて存在を教えない)。

func (*Service) RevokeToken

func (s *Service) RevokeToken(ctx context.Context, plaintext, hint string) (bool, error)

RevokeToken はトークン平文を失効させる(Go API。ADR-0006 §5 の RevokeToken)。

  • Access Token: そのトークンだけを失効させる。同じ Family の Refresh Token は残る。
  • Refresh Token: Family 全体(後継の Refresh Token と全 Access Token)を失効させる (RFC 7009 §2.1 の "SHOULD also invalidate all access tokens based on the same grant")。

戻り値はトークンが見つかったか。不明・失効済みでもエラーにしない。

Example

ExampleService_RevokeToken は管理画面やサポート対応から、トークンを 1 本だけ失効させる。 Refresh Token を渡すと、その Token Family(後継の Refresh Token と全 Access Token)がまとめて失効する。 ユーザーと Client の連携そのものを解除するなら grant.Service.Revoke を使う。

package main

import (
	"context"
	"fmt"
	"log"
	"log/slog"
	"time"

	authengine "github.com/u-masato/auth-engine"
	"github.com/u-masato/auth-engine/client"
	"github.com/u-masato/auth-engine/keys"
	"github.com/u-masato/auth-engine/storage/memory"
	"github.com/u-masato/auth-engine/token"
)

// exampleEngine は Machine クライアント svc を登録した Engine と、svc に client_credentials で
// 発行した Access Token を返す。
func exampleEngine(ctx context.Context) (*authengine.Engine, string) {
	now := time.Now()
	store := memory.New()
	svc, secret, err := client.NewMachine(client.MachineParams{ID: "svc", Name: "Service", Scopes: []string{"api.read"}}, now)
	if err != nil {
		log.Fatal(err)
	}
	if err := store.CreateClient(ctx, svc); err != nil {
		log.Fatal(err)
	}
	ks := keys.NewSet()
	k, err := keys.Generate(keys.AlgRS256, now)
	if err != nil {
		log.Fatal(err)
	}
	if err := ks.AddSigning(k); err != nil {
		log.Fatal(err)
	}
	engine, err := authengine.New(authengine.Config{Issuer: "https://as.example.com", Store: store, Keys: ks, Logger: slog.New(slog.DiscardHandler)})
	if err != nil {
		log.Fatal(err)
	}
	res, err := engine.Token.Exchange(ctx, token.Request{
		GrantType:   "client_credentials",
		Credentials: token.Credentials{ClientID: "svc", Secret: string(secret), Method: client.AuthClientSecretBasic},
	})
	if err != nil {
		log.Fatal(err)
	}
	return engine, res.AccessToken
}

func main() {
	ctx := context.Background()
	engine, accessToken := exampleEngine(ctx)

	found, err := engine.Token.RevokeToken(ctx, accessToken, token.HintAccessToken)
	if err != nil {
		log.Fatal(err)
	}
	info, err := engine.Token.Inspect(ctx, accessToken, token.HintAccessToken)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("found:", found, "active:", info.Active)

}
Output:
found: true active: false

type Store

type Store interface {
	// ConsumeAuthorizationCode は Code を消費済みにして内容を返す。
	//
	//   - 未消費かつ未期限: 消費済みにして Code を返す。
	//   - 既に消費済み: 消費済みの Code と storage.ErrCodeAlreadyUsed を返す。
	//     呼び出し側は Code.FamilyID の Family を失効させる。
	//   - 存在しない、または期限切れ: storage.ErrNotFound。
	//
	// 消費済みの Code は再利用検知のため、期限まで削除しない。
	ConsumeAuthorizationCode(ctx context.Context, codeHash string, now time.Time) (*authorization.AuthorizationCode, error)

	// IssueTokens は新しい Family と、それに属する最初のトークンを保存する。
	// refresh は nil でよい。Family の GrantID が指す Grant が active でなければ
	// storage.ErrGrantRevoked を返し、何も保存しない。
	IssueTokens(ctx context.Context, family *Family, access *AccessToken, refresh *RefreshToken) error

	// RotateRefreshToken は oldHash の Refresh Token を rotated にし、同じ Family に
	// next(Refresh Token)と access(Access Token)を保存する。
	//
	//   - oldHash が active かつ未期限: 成功。
	//   - oldHash が rotated: storage.ErrTokenReused。呼び出し側は Family を失効させる。
	//   - oldHash が revoked、または Family が revoked: storage.ErrFamilyRevoked。
	//   - Grant が revoked: storage.ErrGrantRevoked。
	//   - 存在しない、または期限切れ: storage.ErrNotFound。
	RotateRefreshToken(ctx context.Context, oldHash string, next *RefreshToken, access *AccessToken, now time.Time) error

	// LookupAccessToken は Access Token と、それが属する Grant を返す。
	// Grant の失効チェックを二重化するため、Grant も併せて返す。なければ storage.ErrNotFound。
	LookupAccessToken(ctx context.Context, hash string) (*AccessToken, *grant.Grant, error)

	// LookupRefreshToken は Refresh Token を返す。なければ storage.ErrNotFound。
	LookupRefreshToken(ctx context.Context, hash string) (*RefreshToken, error)

	// GetTokenFamily は Family を返す。なければ storage.ErrNotFound。
	// Refresh Token は Family を介して Client・subject・scope に結びつく。
	GetTokenFamily(ctx context.Context, id string) (*Family, error)

	// RevokeTokenFamily は Family と配下の全トークンを失効させる。
	// 既に失効済みなら何もせず nil。存在しなければ storage.ErrNotFound。
	RevokeTokenFamily(ctx context.Context, familyID string, now time.Time) error

	// RevokeAccessToken は Access Token 1 つを失効させる。存在しなければ storage.ErrNotFound。
	RevokeAccessToken(ctx context.Context, hash string, now time.Time) error
}

Store はトークンの永続化 Port。各メソッドは単体で原子的でなければならない(ADR-0005 §2)。

Jump to

Keyboard shortcuts

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