deviceauth

package
v0.27.2 Latest Latest
Warning

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

Go to latest
Published: Aug 31, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// EventDeviceCodeRequested is published when a new device authorization request is initiated.
	EventDeviceCodeRequested = "deviceauth:code_requested"

	// EventDeviceCodeApproved is published when an authenticated user approves a device code.
	EventDeviceCodeApproved = "eventauth:code_approved"

	// EventDeviceCodeDenied is published when a user explicitly rejects a device code request.
	EventDeviceCodeDenied = "deviceauth:code_denied"

	// EventDeviceTokenExchanged is published when an approved device code is successfully exchanged for a session token.
	EventDeviceTokenExchanged = "deviceauth:token_exchanged"
)
View Source
const DefaultCharset = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"

DefaultCharset contains human-friendly uppercase letters and numbers, excluding ambiguous characters (0, O, 1, I, L).

View Source
const OAuth2DeviceGrantType = "urn:ietf:params:oauth:grant-type:device_code"

OAuth2DeviceGrantType is the standard RFC 8628 grant type string.

View Source
const PluginID = "device-authorization"

PluginID is the unique string identifier for the Device Authorization plugin ("device-authorization").

Variables

View Source
var (
	// ErrInvalidDeviceCode is returned when a provided device code does not exist in storage.
	ErrInvalidDeviceCode = errors.New("deviceauth: invalid device code")

	// ErrInvalidUserCode is returned when a provided user verification code does not exist in storage.
	ErrInvalidUserCode = errors.New("deviceauth: invalid user verification code")

	// ErrCodeExpired is returned when attempting to exchange or authorize a device code that has passed its expiration time.
	ErrCodeExpired = errors.New("deviceauth: device code has expired")

	// ErrSlowDown is returned when client polling frequency exceeds the configured interval.
	ErrSlowDown = errors.New("deviceauth: polling rate limit exceeded (slow_down)")

	// ErrAuthorizationPending is returned when polling a device code that is still waiting for user approval.
	ErrAuthorizationPending = errors.New("deviceauth: authorization pending")

	// ErrAccessDenied is returned when polling a device code that has been explicitly denied by the user.
	ErrAccessDenied = errors.New("deviceauth: authorization request denied")

	// ErrAlreadyConsumed is returned when attempting to consume a device code that was already exchanged.
	ErrAlreadyConsumed = errors.New("deviceauth: device code has already been consumed")

	// ErrInvalidGrantType is returned when the exchange request grant_type is not "urn:ietf:params:oauth:grant-type:device_code".
	ErrInvalidGrantType = errors.New("deviceauth: invalid grant_type")

	// ErrUserNotFound is returned when the owner user record cannot be found upon token exchange.
	ErrUserNotFound = errors.New("deviceauth: user not found")

	// ErrInvalidClientID is returned when client_id validation fails or mismatch occurs.
	ErrInvalidClientID = errors.New("deviceauth: invalid client_id")

	// ErrInvalidParameter is returned when required parameters are missing or malformed.
	ErrInvalidParameter = errors.New("deviceauth: required parameter is missing or invalid")
)

Sentinel errors for the Device Authorization plugin.

Functions

func BuildVerificationURIs

func BuildVerificationURIs(baseURI, customURI, userCode string) (uri string, uriComplete string)

BuildVerificationURIs constructs the standard verification_uri and verification_uri_complete URLs.

func DefaultGenerateDeviceCode

func DefaultGenerateDeviceCode(length int) (string, error)

DefaultGenerateDeviceCode generates a cryptographically secure random hex device code string.

func DefaultGenerateUserCode

func DefaultGenerateUserCode(length int) (string, error)

DefaultGenerateUserCode generates a cryptographically secure user verification code using DefaultCharset.

func NormalizeUserCode

func NormalizeUserCode(code string) string

NormalizeUserCode strips spaces, hyphens, and converts the input string to uppercase.

Types

type ApproveDeviceCodeParams

type ApproveDeviceCodeParams struct {
	// UserID is the authenticated user approving the grant.
	UserID string `json:"user_id"`

	// UserCode is the user verification code submitted by the user.
	UserCode string `json:"user_code"`
}

ApproveDeviceCodeParams holds input parameters when an authenticated user approves a device authorization.

type Config

type Config struct {
	// ExpiresIn specifies the lifetime duration of a device code grant (default: 30 minutes).
	ExpiresIn time.Duration

	// Interval specifies the minimum polling interval expected between token requests (default: 5 seconds).
	Interval time.Duration

	// DeviceCodeLength specifies the random character length for generated device codes (default: 40).
	DeviceCodeLength int

	// UserCodeLength specifies the random character length for generated user codes (default: 8).
	UserCodeLength int

	// VerificationURI specifies the relative or absolute user verification path (default: "/device").
	VerificationURI string

	// CustomURI optionally specifies a custom base URI for completing verification URLs.
	CustomURI string

	// SessionExpiry specifies the default duration of sessions created upon token exchange (default: 24 hours).
	SessionExpiry time.Duration

	// GenerateDeviceCode allows overriding the default device code generator.
	GenerateDeviceCode func(length int) (string, error)

	// GenerateUserCode allows overriding the default user code generator.
	GenerateUserCode func(length int) (string, error)

	// ValidateClient is an optional hook to validate client_id during device code authorization requests.
	ValidateClient func(ctx context.Context, clientID string) (bool, error)

	// OnDeviceAuthRequest is an optional hook executed during device code authorization requests.
	OnDeviceAuthRequest func(ctx context.Context, clientID string, scope *string) error
}

Config holds all configuration settings for the Device Authorization plugin.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns a Config struct pre-populated with standard RFC 8628 defaults.

type DenyDeviceCodeParams

type DenyDeviceCodeParams struct {
	// UserCode is the user verification code submitted by the user.
	UserCode string `json:"user_code"`
}

DenyDeviceCodeParams holds input parameters when an authenticated user rejects a device authorization.

type DeviceCode

type DeviceCode struct {
	// ID is the unique database record identifier.
	ID string `json:"id"`

	// DeviceCode is the high-entropy secret issued to the device for polling.
	DeviceCode string `json:"device_code"`

	// UserCode is the short, human-readable code presented to the user for verification.
	UserCode string `json:"user_code"`

	// UserID is the owner user's unique identifier, populated upon user authorization.
	UserID *string `json:"user_id,omitempty"`

	// ExpiresAt specifies the exact timestamp after which this device code grant is invalid.
	ExpiresAt time.Time `json:"expires_at"`

	// Status represents the current state of authorization (pending, approved, denied).
	Status DeviceCodeStatus `json:"status"`

	// LastPolledAt records the timestamp of the most recent token polling request.
	LastPolledAt *time.Time `json:"last_polled_at,omitempty"`

	// PollingInterval specifies the minimum duration between consecutive polling attempts.
	PollingInterval time.Duration `json:"polling_interval"`

	// ClientID optionally identifies the client application requesting authorization.
	ClientID *string `json:"client_id,omitempty"`

	// Scope optionally specifies the requested access scope.
	Scope *string `json:"scope,omitempty"`

	// CreatedAt records when the device authorization grant was generated.
	CreatedAt time.Time `json:"created_at"`

	// UpdatedAt records when the device authorization grant state was last modified.
	UpdatedAt time.Time `json:"updated_at"`
}

DeviceCode represents a persistent device authorization request grant (RFC 8628).

type DeviceCodeApprovedPayload

type DeviceCodeApprovedPayload struct {
	UserCode string `json:"user_code"`
	UserID   string `json:"user_id"`
}

DeviceCodeApprovedPayload defines the event bus payload dispatched when a user authorizes a grant.

type DeviceCodeDeniedPayload

type DeviceCodeDeniedPayload struct {
	UserCode string `json:"user_code"`
}

DeviceCodeDeniedPayload defines the event bus payload dispatched when a user rejects a grant.

type DeviceCodeRequestedPayload

type DeviceCodeRequestedPayload struct {
	DeviceCode string  `json:"device_code"`
	UserCode   string  `json:"user_code"`
	ClientID   *string `json:"client_id,omitempty"`
	Scope      *string `json:"scope,omitempty"`
}

DeviceCodeRequestedPayload defines the event bus payload dispatched on device code creation.

type DeviceCodeResponse

type DeviceCodeResponse struct {
	// DeviceCode is the verification code issued to the client.
	DeviceCode string `json:"device_code"`

	// UserCode is the short verification code for human entry.
	UserCode string `json:"user_code"`

	// VerificationURI is the end-user verification URL on the authorization server.
	VerificationURI string `json:"verification_uri"`

	// VerificationURIComplete is the optional verification URL pre-filled with the user_code.
	VerificationURIComplete string `json:"verification_uri_complete,omitempty"`

	// ExpiresIn indicates the lifetime in seconds of the device_code and user_code.
	ExpiresIn int64 `json:"expires_in"`

	// Interval indicates the minimum number of seconds the client should wait between polling requests.
	Interval int64 `json:"interval"`
}

DeviceCodeResponse represents the successful RFC 8628 response returned to the device.

type DeviceCodeStatus

type DeviceCodeStatus string

DeviceCodeStatus represents the authorization status of a device code.

const (
	// StatusPending indicates the user has not yet approved or denied the device authorization request.
	StatusPending DeviceCodeStatus = "pending"

	// StatusApproved indicates the user has successfully authorized the device request.
	StatusApproved DeviceCodeStatus = "approved"

	// StatusDenied indicates the user has explicitly rejected the device authorization request.
	StatusDenied DeviceCodeStatus = "denied"
)

type DeviceTokenExchangedPayload

type DeviceTokenExchangedPayload struct {
	DeviceCode   string `json:"device_code"`
	UserID       string `json:"user_id"`
	SessionToken string `json:"session_token"`
}

DeviceTokenExchangedPayload defines the event bus payload dispatched on successful token issuance.

type ExchangeDeviceTokenParams

type ExchangeDeviceTokenParams struct {
	// GrantType must be "urn:ietf:params:oauth:grant-type:device_code".
	GrantType string `json:"grant_type"`

	// DeviceCode is the device code issued in the initial authorization response.
	DeviceCode string `json:"device_code"`

	// ClientID optionally identifies the client making the request.
	ClientID *string `json:"client_id,omitempty"`
}

ExchangeDeviceTokenParams holds parameters submitted by the device when polling for a session token.

type Option

type Option func(*Config)

Option represents a functional option for configuring the Device Authorization plugin.

func WithCustomURI

func WithCustomURI(uri string) Option

WithCustomURI sets a custom domain/host URI used for completing verification URLs.

func WithDeviceCodeLength

func WithDeviceCodeLength(length int) Option

WithDeviceCodeLength sets the character length of generated device codes.

func WithExpiresIn

func WithExpiresIn(d time.Duration) Option

WithExpiresIn sets the expiration duration of issued device code grants.

func WithGenerateDeviceCode

func WithGenerateDeviceCode(fn func(length int) (string, error)) Option

WithGenerateDeviceCode overrides the default cryptographic device code generator function.

func WithGenerateUserCode

func WithGenerateUserCode(fn func(length int) (string, error)) Option

WithGenerateUserCode overrides the default cryptographic user code generator function.

func WithInterval

func WithInterval(d time.Duration) Option

WithInterval sets the minimum polling interval requirement.

func WithOnDeviceAuthRequest

func WithOnDeviceAuthRequest(fn func(ctx context.Context, clientID string, scope *string) error) Option

WithOnDeviceAuthRequest registers a hook executed when a device code is requested.

func WithSessionExpiry

func WithSessionExpiry(d time.Duration) Option

WithSessionExpiry sets the duration of sessions generated when exchanging an approved device code.

func WithUserCodeLength

func WithUserCodeLength(length int) Option

WithUserCodeLength sets the character length of generated user verification codes.

func WithValidateClient

func WithValidateClient(fn func(ctx context.Context, clientID string) (bool, error)) Option

WithValidateClient registers a callback to validate client_id strings.

func WithVerificationURI

func WithVerificationURI(uri string) Option

WithVerificationURI sets the base verification URI returned in device code responses.

type Plugin

type Plugin struct {
	// contains filtered or unexported fields
}

Plugin implements the RFC 8628 Device Authorization Flow plugin for go-modular-auth.

func New

func New(repo Repository, opts ...Option) *Plugin

New instantiates a new Device Authorization plugin configured with the given repository and options.

func (*Plugin) ApproveDeviceCode

func (p *Plugin) ApproveDeviceCode(ctx context.Context, params ApproveDeviceCodeParams) error

ApproveDeviceCode approves a pending device authorization request for an authenticated user.

func (*Plugin) Config

func (p *Plugin) Config() Config

Config returns a copy of the active plugin configuration.

func (*Plugin) DenyDeviceCode

func (p *Plugin) DenyDeviceCode(ctx context.Context, params DenyDeviceCodeParams) error

DenyDeviceCode rejects a pending device authorization request.

func (*Plugin) ExchangeDeviceToken

func (p *Plugin) ExchangeDeviceToken(ctx context.Context, params ExchangeDeviceTokenParams) (*TokenResponse, error)

ExchangeDeviceToken polls for authorization and exchanges an approved device_code for a session access token.

func (*Plugin) GetVerificationState

func (p *Plugin) GetVerificationState(ctx context.Context, rawUserCode string) (*DeviceCode, error)

GetVerificationState retrieves the device authorization grant state by user_code.

func (*Plugin) ID

func (p *Plugin) ID() string

ID returns the unique string identifier for the plugin ("device-authorization").

func (*Plugin) Init

func (p *Plugin) Init(ctx *plugin.Context) error

Init initializes the plugin with the shared execution context.

func (*Plugin) RequestDeviceCode

func (p *Plugin) RequestDeviceCode(ctx context.Context, params RequestDeviceCodeParams) (*DeviceCodeResponse, error)

RequestDeviceCode initiates a new device authorization grant request.

func (*Plugin) ServeApprove added in v0.20.0

func (p *Plugin) ServeApprove(w http.ResponseWriter, r *http.Request)

ServeApprove is a net/http handler for user approval of pending device codes.

func (*Plugin) ServeDeviceCode added in v0.20.0

func (p *Plugin) ServeDeviceCode(w http.ResponseWriter, r *http.Request)

ServeDeviceCode is a net/http handler for processing RFC 8628 device authorization requests.

func (*Plugin) ServeTokenExchange added in v0.20.0

func (p *Plugin) ServeTokenExchange(w http.ResponseWriter, r *http.Request)

ServeTokenExchange is a net/http handler for polling and exchanging approved device codes for tokens.

type RFCErrorResponse

type RFCErrorResponse struct {
	// Error is the RFC error string (e.g. "authorization_pending", "slow_down", "expired_token", "access_denied").
	Error string `json:"error"`

	// ErrorDescription provides human-readable details regarding the error.
	ErrorDescription string `json:"error_description,omitempty"`
}

RFCErrorResponse represents a standard OAuth 2.0 / RFC 8628 error payload.

type Repository

type Repository interface {
	// CreateDeviceCode persists a new device authorization grant record in storage.
	//
	// Function:
	//   Called during RFC 8628 device authorization initiation (`/device/code`).
	//
	// Storage:
	//   Cache (Redis / In-Memory TTL) - Short-lived device code state with expiration TTL.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - code: DeviceCode entity containing device_code, user_code, verification_uri, and expiration.
	//
	// Returns:
	//   - error: Nil on success, or database error.
	//
	// Example SQL:
	//   INSERT INTO device_codes (id, device_code, user_code, verification_uri, status, expires_at, created_at)
	//   VALUES ($1, $2, $3, $4, $5, $6, $7);
	//
	// Example Cache (Redis):
	//   err := rdb.Set(ctx, "device:code:" + code.DeviceCode, bytes, ttl).Err()
	CreateDeviceCode(ctx context.Context, code *DeviceCode) error

	// FindByDeviceCode retrieves an active device code record by its device_code secret.
	//
	// Function:
	//   Used during client polling (`/device/token`) to inspect grant status and rate limits.
	//
	// Storage:
	//   Cache (Redis / In-Memory TTL) - Ephemeral device code lookup during client polling.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - deviceCode: Raw device code secret string.
	//
	// Returns:
	//   - *DeviceCode: Matching device code record if found.
	//   - error: ErrInvalidDeviceCode if missing, or database error.
	//
	// Example SQL:
	//   SELECT id, device_code, user_code, status, user_id, last_polled_at, expires_at, created_at FROM device_codes WHERE device_code = $1 LIMIT 1;
	//
	// Example Cache (Redis):
	//   val, err := rdb.Get(ctx, "device:code:" + deviceCode).Bytes()
	FindByDeviceCode(ctx context.Context, deviceCode string) (*DeviceCode, error)

	// FindByUserCode retrieves an active device code record by its normalized user_code.
	//
	// Function:
	//   Used when a end-user enters their short user code on the device authorization web page.
	//
	// Storage:
	//   Cache (Redis / In-Memory TTL) - Lookup device grant state by user_code string.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - userCode: User verification code string (e.g. "WDJB-MJHT").
	//
	// Returns:
	//   - *DeviceCode: Matching device code record if found.
	//   - error: ErrInvalidUserCode if missing, or database error.
	//
	// Example SQL:
	//   SELECT id, device_code, user_code, status, user_id, expires_at, created_at FROM device_codes WHERE user_code = $1 LIMIT 1;
	//
	// Example Cache (Redis):
	//   val, err := rdb.Get(ctx, "device:usercode:" + userCode).Bytes()
	FindByUserCode(ctx context.Context, userCode string) (*DeviceCode, error)

	// UpdateLastPolledAt updates the last_polled_at timestamp for rate limiting checks.
	//
	// Function:
	//   Called during client polling to enforce minimum polling intervals (preventing spam).
	//
	// Storage:
	//   Cache (Redis / In-Memory TTL) - Poll timestamp update.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - deviceCode: Target device code secret.
	//   - polledAt: Timestamp of current poll request.
	//
	// Returns:
	//   - error: Nil on success.
	//
	// Example SQL:
	//   UPDATE device_codes SET last_polled_at = $1 WHERE device_code = $2;
	//
	// Example Cache (Redis):
	//   err := rdb.Set(ctx, "device:polled:" + deviceCode, polledAt.Unix(), ttl).Err()
	UpdateLastPolledAt(ctx context.Context, deviceCode string, polledAt time.Time) error

	// UpdateStatus updates the status (approved/denied) and optional owner userID for a device code by userCode.
	//
	// Function:
	//   Called when an authenticated user approves or denies the device authorization request.
	//
	// Storage:
	//   Cache (Redis / In-Memory TTL) - Grant status approval update.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - userCode: User verification code string.
	//   - status: StatusApproved or StatusDenied.
	//   - userID: Pointer to approving user ID.
	//
	// Returns:
	//   - error: Nil on success.
	//
	// Example SQL:
	//   UPDATE device_codes SET status = $1, user_id = $2 WHERE user_code = $3;
	//
	// Example Cache (Redis):
	//   err := rdb.Set(ctx, "device:status:" + userCode, status, ttl).Err()
	UpdateStatus(ctx context.Context, userCode string, status DeviceCodeStatus, userID *string) error

	// ConsumeDeviceCode atomically retrieves and removes/invalidates an approved device code record.
	// This operation MUST be single-use to protect against race conditions during concurrent polls.
	//
	// Function:
	//   Called when client polling detects approval and exchanges the device code for access tokens.
	//
	// Storage:
	//   Cache (Redis GETDEL / Memory) - Atomic read-and-delete single-use consumption.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - deviceCode: Raw device code secret.
	//
	// Returns:
	//   - *DeviceCode: Consumed device code grant entity.
	//   - error: ErrAlreadyConsumed if already exchanged, ErrAuthorizationPending if not approved.
	//
	// Example SQL:
	//   DELETE FROM device_codes WHERE device_code = $1 AND status = 'approved' RETURNING id, device_code, user_code, status, user_id, expires_at, created_at;
	//
	// Example Cache (Redis):
	//   val, err := rdb.GetDel(ctx, "device:code:" + deviceCode).Bytes()
	ConsumeDeviceCode(ctx context.Context, deviceCode string) (*DeviceCode, error)

	// DeleteDeviceCode removes a device code record from persistent storage.
	//
	// Function:
	//   Called during explicit cancellation or removal.
	//
	// Storage:
	//   Cache (Redis / In-Memory TTL) - Device code eviction.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - deviceCode: Device code secret.
	//
	// Returns:
	//   - error: Nil on success.
	//
	// Example SQL:
	//   DELETE FROM device_codes WHERE device_code = $1;
	//
	// Example Cache (Redis):
	//   err := rdb.Del(ctx, "device:code:" + deviceCode).Err()
	DeleteDeviceCode(ctx context.Context, deviceCode string) error

	// DeleteExpiredDeviceCodes purges all expired device code records.
	//
	// Function:
	//   Called by background cleanup crons.
	//
	// Storage:
	//   Database (GORM / SQL) - Bulk record deletion.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//
	// Returns:
	//   - error: Nil on success.
	//
	// Example SQL:
	//   DELETE FROM device_codes WHERE expires_at <= $1;
	DeleteExpiredDeviceCodes(ctx context.Context) error

	// GetUserByID retrieves a user entity by unique user identifier.
	//
	// Function:
	//   Used after successful device code exchange to create user session and populate identity.
	//
	// Storage:
	//   Database (GORM / SQL) - User primary key lookup.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - userID: Target user primary key.
	//
	// Returns:
	//   - *entity.User: Matching user entity if found.
	//   - error: ErrUserNotFound if missing.
	//
	// Example SQL:
	//   SELECT id, email, name, email_verified, created_at, updated_at FROM users WHERE id = $1 LIMIT 1;
	GetUserByID(ctx context.Context, userID string) (*entity.User, error)

	// CreateSession initializes and persists an active user session entity.
	//
	// Function:
	//   Called during successful token exchange to establish a new user session.
	//
	// Storage:
	//   Database (GORM / SQL) - Active session creation.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - session: Active session entity to persist.
	//
	// Returns:
	//   - *entity.Session: Created session entity.
	//   - error: Nil on success.
	//
	// Example SQL:
	//   INSERT INTO sessions (id, user_id, token, expires_at, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6);
	CreateSession(ctx context.Context, session *entity.Session) (*entity.Session, error)
}

Repository defines the persistent storage contract required by the Device Authorization plugin. Implement this interface on your custom database adapter (e.g. PostgreSQL, MySQL, SQLite, MongoDB, GORM).

Implementation Example (GORM / database/sql):

type GormDeviceAuthRepository struct {
	db *gorm.DB
}

func (r *GormDeviceAuthRepository) FindByDeviceCode(ctx context.Context, deviceCode string) (*deviceauth.DeviceCode, error) {
	var dc deviceauth.DeviceCode
	if err := r.db.WithContext(ctx).Where("device_code = ?", deviceCode).First(&dc).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, deviceauth.ErrInvalidDeviceCode
		}
		return nil, err
	}
	return &dc, nil
}

type RequestDeviceCodeParams

type RequestDeviceCodeParams struct {
	// ClientID identifies the client application requesting authorization.
	ClientID string `json:"client_id"`

	// UserID optionally pre-associates a user ID with the request.
	UserID *string `json:"user_id,omitempty"`

	// Scope optionally requests specific access permissions.
	Scope *string `json:"scope,omitempty"`
}

RequestDeviceCodeParams holds the input parameters for requesting a device code authorization grant.

type TokenResponse

type TokenResponse struct {
	// AccessToken is the generated session or access token.
	AccessToken string `json:"access_token"`

	// TokenType is the token authorization scheme (typically "Bearer").
	TokenType string `json:"token_type"`

	// ExpiresIn is the session lifetime in seconds.
	ExpiresIn int64 `json:"expires_in"`

	// Scope optionally returns the granted access scope.
	Scope string `json:"scope,omitempty"`

	// UserID is the authenticated user ID associated with the session.
	UserID string `json:"user_id,omitempty"`
}

TokenResponse represents a successful token issuance response following device authorization.

Jump to

Keyboard shortcuts

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