Documentation
¶
Overview ¶
Package singpass wires the FAPIgo relying-party engine (github.com/idfoundry/fapigo/client) to talk to the Singpass / Corppass FAPI 2.0 profile. FAPIgo does all of the protocol work; this package supplies the pieces FAPIgo leaves to the embedder — a key manager, a keys.Decrypter for the encrypted id_token, and a session store — and encodes the Singpass-specific choices (PAR parameters, algorithm suite, /userinfo handling) so a relying party can integrate Singpass Login, Myinfo, and Myinfo Business without rediscovering them.
Most callers should use the per-product constructors NewLogin, NewMyinfo, and NewMyinfoBusiness (see products.go), which take just key material and a client_id. New is the lower-level constructor for full control or a non-standard product.
Index ¶
- Constants
- Variables
- func NewAgreerDecrypter(agreer keys.ECDHAgreer) (keys.Decrypter, error)
- func NewECDHDecrypter(encKey *ecdsa.PrivateKey, encKID string) (keys.Decrypter, error)
- func NewKeyManager(sig crypto.Signer, sigKID string) (keys.KeyManager, error)
- func NewMemorySessionStore(maxPending int) storage.SessionStore
- func OfflineClientJWKS(ctx context.Context, sigKey *ecdsa.PrivateKey, sigKID string, ...) ([]byte, error)
- func RecommendedLimits(httpTimeout time.Duration) client.Limits
- type ActingParty
- type Client
- func New(ctx context.Context, opts Options, deps Dependencies) (*Client, error)
- func NewLogin(ctx context.Context, o LoginOptions, deps Dependencies) (*Client, error)
- func NewMyinfo(ctx context.Context, o MyinfoOptions, deps Dependencies) (*Client, error)
- func NewMyinfoBusiness(ctx context.Context, o MyinfoBusinessOptions, deps Dependencies) (*Client, error)
- type DeniedError
- type Dependencies
- type Identity
- func (id *Identity) ActingParty() *ActingParty
- func (id *Identity) AssuranceContext() string
- func (id *Identity) AssuranceLevel() string
- func (id *Identity) Audience() []string
- func (id *Identity) AuthMethods() []string
- func (id *Identity) Issuer() string
- func (id *Identity) SubjectAttributes() map[string]any
- func (id *Identity) SubjectType() string
- type LoginOptions
- type MyinfoBusinessOptions
- type MyinfoOptions
- type Options
Examples ¶
Constants ¶
const ( // StagingSingpassIssuer is the Singpass FAPI 2.0 issuer (Login + Myinfo). StagingSingpassIssuer = "https://stg-id.singpass.gov.sg/fapi" // StagingCorppassIssuer is the Corppass FAPI 2.0 issuer (Myinfo Business). // Note it has no "/fapi" path suffix, unlike Singpass, and it is a separate // authorization server with its own discovery document. StagingCorppassIssuer = "https://stg-id.corppass.gov.sg" )
Staging issuer identifiers. Production integrations pass their own issuer via the product options' Issuer field.
const DefaultAuthContextType = "APP_AUTHENTICATION_DEFAULT"
DefaultAuthContextType is the standard Singpass/Corppass Login authentication_context_type — the flow a plain login authenticates for.
const DefaultMaxPendingLogins = 10_000
DefaultMaxPendingLogins caps how many started-but-unfinished logins the in-memory session store holds at once when NewMemorySessionStore is given 0. At FAPIgo's 5-minute session lifetime that allows ~33 new logins a second, sustained, before new ones are refused.
Variables ¶
var ErrTooManyPendingLogins = errors.New("singpass: too many pending logins")
ErrTooManyPendingLogins is returned (wrapped) by BeginLogin when the in-memory session store is at its cap. It means too many logins have been started and not finished within the session lifetime — typically someone hitting the login route in a loop — and a caller may map it to 503.
Functions ¶
func NewAgreerDecrypter ¶
func NewAgreerDecrypter(agreer keys.ECDHAgreer) (keys.Decrypter, error)
NewAgreerDecrypter builds the keys.Decrypter from a caller-supplied keys.ECDHAgreer instead of an in-memory private key. This is the ergonomic seam for an HSM/KMS deployment: the agreer performs the ECDH key-agreement step (an HSM's CKM_ECDH1_DERIVE or a managed KMS's DeriveSharedSecret) without the encryption private key ever entering this process, while FAPIgo still owns the Concat-KDF + RFC-3394 unwrap. It parallels NewKeyManager accepting a crypto.Signer, so hardware-backed decryption sits on the same footing as hardware-backed signing. One decrypter serves both the id_token and the /userinfo response. There is no kid parameter: the agreer reports its own key id (keys.RecipientKey), which is what the published JWKS and JWE "kid" matching use.
func NewECDHDecrypter ¶
NewECDHDecrypter builds the keys.Decrypter FAPIgo uses to unwrap the JWE content-encryption key of the id_token and /userinfo response. The encryption key stays in process as an in-memory ECDH backend; FAPIgo owns the ECDH-ES Concat-KDF + RFC-3394 key-unwrap. One decrypter serves both the id_token and the /userinfo response (keys.IDTokenDecryption + keys.UserInfoDecryption).
The private key never leaves this process. A KMS/HSM deployment would instead implement keys.ECDHAgreer / keys.KeyDecrypter over its own backend and inject the result via Dependencies.Decryption.
func NewKeyManager ¶
NewKeyManager builds the keys.KeyManager FAPIgo uses for the two signing operations a relying party performs:
- keys.ClientAuthentication — signs the private_key_jwt client assertion with the persistent EC key whose public half is registered with the authorization server under "use":"sig".
- keys.DPoPProofSigning — signs DPoP proofs (RFC 9449) with an ephemeral EC key generated here at startup. A DPoP key is sender-constraining and per-instance; it is never pre-registered, so it deliberately does not survive a restart.
sig is any crypto.Signer over an ES256 / P-256 key: a plain *ecdsa.PrivateKey held in memory, or an HSM/KMS-backed signer. FAPIgo's keys.NewKeyManagerFromSigners adapts it directly — it routes the pre-hashed digest, selects crypto.SignerOpts, and validates the ES256 / P-256 curve requirement at construction — so there is no signing glue here.
The id_token encryption key is NOT handled here — FAPIgo never signs with it. It is used only for JWE decryption, driven through the separate keys.Decrypter (see NewECDHDecrypter), which is why it lives outside the KeyManager contract.
func NewMemorySessionStore ¶
func NewMemorySessionStore(maxPending int) storage.SessionStore
NewMemorySessionStore returns an in-memory storage.SessionStore for FAPIgo's in-flight authorization state (state, nonce, PKCE verifier) between BeginLogin and Complete. It is the default when Dependencies.Sessions is nil.
Unlike FAPIgo's memstore, which keeps a session until its callback arrives, it drops a session once its ExpiresAt passes, so logins that are started and never finished do not accumulate. It also refuses new sessions beyond maxPending (0 means DefaultMaxPendingLogins) with ErrTooManyPendingLogins, so memory stays bounded however fast the login route is hit.
It is still per-process and non-durable, declares no storage.StoreAssurance, and so — like memstore — is refused under client.AssuranceProduction.
func OfflineClientJWKS ¶
func OfflineClientJWKS(ctx context.Context, sigKey *ecdsa.PrivateKey, sigKID string, encKey *ecdsa.PrivateKey, encKID string) ([]byte, error)
OfflineClientJWKS builds the public JWKS a relying party publishes during onboarding (the client-authentication signing key + the id_token/userinfo encryption key), without contacting the authorization server. Key-generation tooling runs before any client_id or discovery document exists, so there is no Client to call Client.PublicJWKS on yet.
FAPIgo's keys.PublicJWKS assembles the set straight from key material and the declared algorithms — no client engine, issuer or endpoints required — so the caller carries no JWK marshaling of its own. It is the same library code the live client's PublicJWKS resolves through, so the offline and online sets are produced identically: the signing key under the ClientAuthentication purpose and the encryption key under IDTokenDecryption (the DPoP key is deliberately not published).
Example ¶
Generate the two keys a client registers during onboarding and print the public JWKS to submit — no client_id or network access needed yet.
package main
import (
"context"
"encoding/json"
"fmt"
"log"
singpass "github.com/osanderson/singpass-client-go"
"github.com/osanderson/singpass-client-go/keyfile"
)
func main() {
sig, _ := keyfile.GenerateECKey()
enc, _ := keyfile.GenerateECKey()
// Persist both with keyfile.MarshalECPrivateKeyPEM (file mode 0600).
jwks, err := singpass.OfflineClientJWKS(context.Background(), sig, "login-sig-1", enc, "login-enc-1")
if err != nil {
log.Fatal(err)
}
var set struct {
Keys []struct {
Kid string `json:"kid"`
Use string `json:"use"`
Crv string `json:"crv"`
} `json:"keys"`
}
if err := json.Unmarshal(jwks, &set); err != nil {
log.Fatal(err)
}
for _, k := range set.Keys {
fmt.Println(k.Kid, k.Use, k.Crv)
}
}
Output: login-sig-1 sig P-256 login-enc-1 enc P-256
func RecommendedLimits ¶
RecommendedLimits starts from FAPIgo's conservative defaults and applies the three a Singpass/Corppass relying party genuinely needs to override:
- MaxIDTokenLifetime — RecommendedLimits leaves this zero on purpose (it depends on the specific issuer), and client.New rejects zero. Singpass stays well under 10m but Corppass Myinfo Business issues a longer-lived id_token, so allow up to an hour.
- HTTPTimeout — the per-call bound to the authorization server.
- MaxJOSECompactBytes — RecommendedLimits defaults this to 16 KiB, which a broad-scope Myinfo /userinfo response overshoots: requesting the full person-data scope set yields an ~23 KiB response JWE (and an even larger one for a person carrying many vehicle/children/CPF records), and the cap bounds both the outer JWE and the decrypted inner JWS. 256 KiB gives ample headroom while staying well under MaxHTTPResponseBytes (1 MiB). Only the id_token and /userinfo are affected; fixed-shape artifacts (DPoP proofs, client assertions) keep the library's 16 KiB default.
Everything else (client-assertion lifetime, session lifetime, clock skew, HTTP response-size cap) takes the library's recommended value. A caller that needs different bounds sets Dependencies.Limits to its own client.Limits.
Types ¶
type ActingParty ¶
type ActingParty struct {
Subject string // act.sub — the acting person's identifier
SubjectType string // act.sub_type — typically "user"
}
ActingParty is the "act" (actor) claim: for Corppass Myinfo Business, the person who authenticated on behalf of the entity, distinct from the entity Subject.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a relying party (Login, Myinfo, or Myinfo Business). It is safe for concurrent use.
func New ¶
New discovers the FAPI metadata and constructs a ready-to-use relying-party client. deps.Keys and deps.Decryption are required; every other Dependencies field defaults to a staging-appropriate value when left zero (see Dependencies). Most callers should prefer NewLogin / NewMyinfo / NewMyinfoBusiness.
func NewLogin ¶
func NewLogin(ctx context.Context, o LoginOptions, deps Dependencies) (*Client, error)
NewLogin constructs a Login relying party. It fills in the Login-specific choices (authentication_context_type required, no /userinfo call) and, when deps.Keys / deps.Decryption are nil, builds them from the option's key material. Everything else follows Dependencies' staging defaults.
Example ¶
A Singpass Login client needs only its registered keys, client_id and redirect URI; everything else defaults to Singpass staging.
package main
import (
"context"
"log"
singpass "github.com/osanderson/singpass-client-go"
"github.com/osanderson/singpass-client-go/keyfile"
)
func main() {
sigKey, err := keyfile.LoadECPrivateKey("keys/login/sig.pem")
if err != nil {
log.Fatal(err)
}
encKey, err := keyfile.LoadECPrivateKey("keys/login/enc.pem")
if err != nil {
log.Fatal(err)
}
client, err := singpass.NewLogin(context.Background(), singpass.LoginOptions{
ClientID: "your-client-id",
RedirectURI: "https://app.example.com/login/callback",
Scopes: []string{"openid", "name", "email"},
SigningKey: sigKey, // any crypto.Signer, e.g. an HSM/KMS key
SigningKID: "login-sig-1",
EncryptionKey: encKey,
EncryptionKID: "login-enc-1",
}, singpass.Dependencies{})
if err != nil {
log.Fatal(err)
}
_ = client
}
Output:
func NewMyinfo ¶
func NewMyinfo(ctx context.Context, o MyinfoOptions, deps Dependencies) (*Client, error)
NewMyinfo constructs a Myinfo relying party (authentication + /userinfo person data). It presets FetchUserInfo and omits authentication_context_type.
func NewMyinfoBusiness ¶
func NewMyinfoBusiness(ctx context.Context, o MyinfoBusinessOptions, deps Dependencies) (*Client, error)
NewMyinfoBusiness constructs a Myinfo Business (Corppass) relying party. It presets FetchUserInfo, enables the Corppass /userinfo sub == client_id tolerance, and defaults to the Corppass issuer.
func (*Client) BeginLogin ¶
BeginLogin starts an authorization attempt: it runs the pushed authorization request and returns the URL to redirect the browser to, plus the opaque state handle (usable as a defense-in-depth cookie binding the request to its eventual callback).
func (*Client) Complete ¶
Complete validates the authorization callback (identified by its raw query string), exchanges the code for tokens, and returns the authenticated identity. A user-declined or server-denied login is reported as a *DeniedError.
Example ¶
The whole flow with plain net/http: BeginLogin on the way out, Complete on the callback. The state handle ties the callback to the browser that started the login. (The web package does all of this for you.)
package main
import (
"errors"
"fmt"
"net/http"
singpass "github.com/osanderson/singpass-client-go"
)
func main() {
var client *singpass.Client // from NewLogin / NewMyinfo / NewMyinfoBusiness
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
redirectURL, state, err := client.BeginLogin(r.Context())
if err != nil {
http.Error(w, "login unavailable", http.StatusBadGateway)
return
}
http.SetCookie(w, &http.Cookie{
Name: "sp_state", Value: state, Path: "/login",
MaxAge: 600, HttpOnly: true, Secure: true, SameSite: http.SameSiteLaxMode,
})
http.Redirect(w, r, redirectURL, http.StatusFound)
})
http.HandleFunc("/login/callback", func(w http.ResponseWriter, r *http.Request) {
c, err := r.Cookie("sp_state")
if err != nil || c.Value != r.URL.Query().Get("state") {
http.Error(w, "login expired, please try again", http.StatusBadRequest)
return
}
id, err := client.Complete(r.Context(), r.URL.RawQuery)
var denied *singpass.DeniedError
switch {
case errors.As(err, &denied):
fmt.Fprintf(w, "login cancelled (%s)", denied.Code)
return
case err != nil:
http.Error(w, "login failed", http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "hello %s", id.Subject)
})
}
Output:
func (*Client) PublicJWKS ¶
PublicJWKS returns this client's public JWK Set — the signing key the server uses to verify the private_key_jwt client assertion, and the encryption key it encrypts the id_token/userinfo to. FAPIgo assembles it (client.PublicJWKS) from the KeyManager and the keys.Decrypter this client already holds.
type DeniedError ¶
DeniedError is returned by Complete when the authorization server (or the user) declined the request, as opposed to a protocol or transport failure.
func (*DeniedError) Error ¶
func (e *DeniedError) Error() string
type Dependencies ¶
type Dependencies struct {
// Keys performs this client's signing operations (client assertion + DPoP
// proof). Required. See NewKeyManager.
Keys keys.KeyManager
// Decryption recovers the content-encryption key of the encrypted id_token
// (and /userinfo response). Required. See NewECDHDecrypter.
Decryption keys.Decrypter
// Sessions persists in-progress authorization-flow state. Nil installs
// NewMemorySessionStore(0): per-process and non-durable, but expired
// sessions are dropped and pending logins are capped, so abandoned logins
// cannot grow memory without bound. Fine for a single instance /
// development; refused under AssuranceProduction.
Sessions storage.SessionStore
// Assurance gates how strict FAPIgo's dependency validation is. Zero means
// client.AssuranceDevelopment (permits the in-memory Sessions default).
// client.AssuranceProduction requires a Sessions store declaring
// storage.StoreAssurance (Durable + AtomicConsume), so the in-memory
// default is refused.
Assurance client.AssuranceLevel
// HTTPClient is the base client for discovery, JWKS, PAR, token and
// /userinfo calls. Nil builds &http.Client{Timeout: HTTPTimeout}.
HTTPClient *http.Client
// HTTPTimeout bounds each outbound call. Zero means defaultHTTPTimeout. It
// is also folded into the default Limits (below) and the default HTTPClient.
HTTPTimeout time.Duration
// Clock supplies the current time. Nil means client.SystemClock{}.
Clock client.Clock
// Random is the randomness source for state/nonce/PKCE. Nil means
// crypto/rand.Reader.
Random io.Reader
// Limits bounds token lifetimes and JOSE/HTTP sizes. Nil means
// RecommendedLimits(HTTPTimeout).
Limits *client.Limits
// Algorithms overrides the FAPI algorithm suite. Nil selects the
// Singpass/Corppass suite (ES256; id_token ECDH-ES+A256KW / A256CBC-HS512;
// and, when Options.FetchUserInfo is set, /userinfo A256GCM). When you set
// this, it is used verbatim and the FetchUserInfo augmentation is skipped —
// declare the /userinfo algorithms yourself.
Algorithms *client.Algorithms
// Debug, when true, logs outbound PAR/token/userinfo requests and responses
// (method, URL, form body, sizes) via Logger. The request dump includes the
// client_assertion — enable it only against staging.
Debug bool
// Logger receives Debug output and internal warnings. Nil means
// slog.Default().
Logger *slog.Logger
}
Dependencies are a Client's injected collaborators. Only Keys and Decryption are required; every other field's zero value selects a sensible staging default, so the simple path is Dependencies{Keys: km, Decryption: dec}. A production deployment sets the fields it needs to harden — most importantly a durable Sessions store together with Assurance: client.AssuranceProduction.
type Identity ¶
type Identity struct {
// App is the relying party that produced this identity ("login" / "myinfo").
App string
// Subject is the "sub" — the identifier for this person, as validated from
// the id_token (never an unverified claim).
Subject string
// Claims is the full decoded id_token payload, for display. It has already
// been signature-, issuer-, audience- and nonce-validated by FAPIgo before
// we ever decode it here.
Claims map[string]any
// Scope granted by the authorization server. When the token response omits
// the optional "scope" (RFC 6749 §5.1 permits this when the granted scope
// equals the requested scope — as Singpass Myinfo does), this falls back to
// the requested scope, which under that rule is the granted scope.
Scope string
// IDTokenIssuedAt and IDTokenExpiry are the validated id_token's "iat" and
// "exp" (FAPIgo has already enforced both against the clock). Captured so
// the caller can report the token's exact lifetime (exp − iat).
IDTokenIssuedAt time.Time
IDTokenExpiry time.Time
// Myinfo is the envelope-aware view of the /userinfo person / organisation
// data (decrypted and inner-JWS-verified). Nil for the Login app, which
// makes no userinfo call; non-nil for Myinfo / Myinfo Business, with the
// blocks the response carried populated (see myinfo.Response).
Myinfo *myinfo.Response
}
Identity is the authenticated result of a completed login.
Example ¶
Identity's accessors read the validated id_token claims without any map-casting.
package main
import (
"fmt"
"time"
singpass "github.com/osanderson/singpass-client-go"
)
func main() {
id := &singpass.Identity{
Subject: "a9865837-7bd7-46ac-bef4-42a76a946424",
Claims: map[string]any{
"iss": "https://stg-id.singpass.gov.sg/fapi",
"aud": "your-client-id",
"amr": []any{"pwd", "otp-sms"},
"acr": "urn:singpass:authentication:loa:2",
},
IDTokenIssuedAt: time.Unix(1_700_000_000, 0),
IDTokenExpiry: time.Unix(1_700_000_600, 0),
}
fmt.Println(id.Issuer())
fmt.Println(id.Audience())
fmt.Println(id.AuthMethods())
fmt.Println("LOA", id.AssuranceLevel())
fmt.Println(id.IDTokenExpiry.Sub(id.IDTokenIssuedAt))
}
Output: https://stg-id.singpass.gov.sg/fapi [your-client-id] [pwd otp-sms] LOA 2 10m0s
func (*Identity) ActingParty ¶
func (id *Identity) ActingParty() *ActingParty
ActingParty returns the "act" actor claim, or nil when the token carries none (personal Login / Myinfo, where the subject is the person themselves).
func (*Identity) AssuranceContext ¶
AssuranceContext returns the raw "acr" authentication-context-class the OP asserted (e.g. a "…:loa:2" string). Empty when absent. Use AssuranceLevel for the parsed Level of Assurance.
func (*Identity) AssuranceLevel ¶
AssuranceLevel extracts the Level of Assurance from a Singpass/Corppass acr of the form "…:loa:N" — returning the value after "loa:" (e.g. "2"). Empty when acr is absent or not in that form.
func (*Identity) Audience ¶
Audience returns the id_token "aud" — the client_id(s) the token was issued to. OIDC permits aud to be a single string or an array of strings; this normalises both to a slice (and tolerates FAPIgo's AsMap handing back a native []string). Empty values are dropped; nil when the claim is absent.
func (*Identity) AuthMethods ¶
AuthMethods returns the "amr" authentication-methods references (how the user authenticated), or nil when absent. Like Audience it accepts both a []string (as AsMap yields) and a JSON []any.
func (*Identity) Issuer ¶
Issuer returns the id_token "iss" — the OP that issued the token (the FAPI issuer, already validated). Empty when absent.
func (*Identity) SubjectAttributes ¶
SubjectAttributes returns the "sub_attributes" descriptor claims about the subject — for Corppass Myinfo Business the entity's name / registration number / type / status etc. Its members are flow-specific, so it is returned as the raw object for the caller to read; nil when the claim is absent.
func (*Identity) SubjectType ¶
SubjectType returns the Singpass "sub_type" — "user" for a person, "entity" for an organisation login (Corppass Myinfo Business). Empty when the claim is absent.
type LoginOptions ¶
type LoginOptions struct {
Name string // app slug for logging/Identity; defaults to "login"
Issuer string // FAPI issuer; defaults to StagingSingpassIssuer
ClientID string // client_id issued by the authorization server
RedirectURI string // must match what is registered with the server
Scopes []string // must include "openid"
// AuthContextType is the Login authentication_context_type; defaults to
// DefaultAuthContextType.
AuthContextType string
// AcrValues is the optional requested level of assurance; "" to omit
// (Singpass rejects it unless the client is whitelisted for it).
AcrValues string
// Key material. Used to build Dependencies.Keys / Dependencies.Decryption
// when the caller leaves those nil; ignored when they are set (an HSM/KMS
// caller injects its own).
SigningKey crypto.Signer // ES256 / P-256 client-assertion + DPoP signer
SigningKID string // kid of the registered signing key
EncryptionKey *ecdsa.PrivateKey // id_token decryption key (in-memory)
EncryptionKID string // kid of EncryptionKey; ignored when EncryptionAgreer is set
// EncryptionAgreer is an optional HSM/KMS-backed ECDH agreer for id_token
// decryption, used in place of the in-memory EncryptionKey. When set it wins
// over EncryptionKey (the encryption private key then never enters this
// process), and the kid comes from the agreer itself — EncryptionKID is not
// used. Ignored when Dependencies.Decryption is supplied. See
// NewAgreerDecrypter.
EncryptionAgreer keys.ECDHAgreer
}
LoginOptions configures a Singpass (or Corppass) Login relying party: authentication only, no /userinfo call. authentication_context_type is mandatory for Login and defaults to DefaultAuthContextType.
type MyinfoBusinessOptions ¶
type MyinfoBusinessOptions struct {
Name string // app slug for logging/Identity; defaults to "myinfobiz"
Issuer string // FAPI issuer; defaults to StagingCorppassIssuer
ClientID string // client_id issued by Corppass
RedirectURI string // must match what is registered with the server
Scopes []string // entity.* / user.* / corppass.* scopes; must include "openid"
AcrValues string // optional requested level of assurance; "" to omit
SigningKey crypto.Signer // ES256 / P-256 client-assertion + DPoP signer
SigningKID string // kid of the registered signing key
EncryptionKey *ecdsa.PrivateKey // id_token / userinfo decryption key (in-memory)
EncryptionKID string // kid of EncryptionKey; ignored when EncryptionAgreer is set
// EncryptionAgreer is an optional HSM/KMS-backed ECDH agreer for id_token /
// userinfo decryption, used in place of the in-memory EncryptionKey. When set
// it wins over EncryptionKey (the encryption private key then never enters
// this process), and the kid comes from the agreer itself — EncryptionKID is
// not used. Ignored when Dependencies.Decryption is supplied. See
// NewAgreerDecrypter.
EncryptionAgreer keys.ECDHAgreer
}
MyinfoBusinessOptions configures a Corppass Myinfo Business relying party: the corporate counterpart of Myinfo, on the separate Corppass FAPI 2.0 authorization server. Same protocol as Myinfo, with the Corppass issuer default and the /userinfo sub == client_id tolerance enabled.
type MyinfoOptions ¶
type MyinfoOptions struct {
Name string // app slug for logging/Identity; defaults to "myinfo"
Issuer string // FAPI issuer; defaults to StagingSingpassIssuer
ClientID string // client_id issued by the authorization server
RedirectURI string // must match what is registered with the server
Scopes []string // person-data scopes; must include "openid"
AcrValues string // optional requested level of assurance; "" to omit
SigningKey crypto.Signer // ES256 / P-256 client-assertion + DPoP signer
SigningKID string // kid of the registered signing key
EncryptionKey *ecdsa.PrivateKey // id_token / userinfo decryption key (in-memory)
EncryptionKID string // kid of EncryptionKey; ignored when EncryptionAgreer is set
// EncryptionAgreer is an optional HSM/KMS-backed ECDH agreer for id_token /
// userinfo decryption, used in place of the in-memory EncryptionKey. When set
// it wins over EncryptionKey (the encryption private key then never enters
// this process), and the kid comes from the agreer itself — EncryptionKID is
// not used. Ignored when Dependencies.Decryption is supplied. See
// NewAgreerDecrypter.
EncryptionAgreer keys.ECDHAgreer
}
MyinfoOptions configures a Singpass Myinfo relying party: authentication plus person data retrieved from the DPoP-protected /userinfo endpoint. authentication_context_type is not sent (Singpass rejects it on Myinfo).
type Options ¶
type Options struct {
Name string // app slug ("login" / "myinfo"), for logging and Identity
Issuer string // FAPI issuer, e.g. https://stg-id.singpass.gov.sg/fapi
ClientID string // client_id issued by the authorization server
RedirectURI string // must match what is registered with the server
Scopes []string // includes "openid"
AuthContextType string // authentication_context_type (Login apps only; rejected on Myinfo)
AcrValues string // optional requested level of assurance; "" to omit
FetchUserInfo bool // call the FAPI /userinfo endpoint after token exchange (Myinfo)
// TolerateUserInfoSubjectClientID accepts a /userinfo "sub" equal to the
// client_id (as well as the id_token sub) — a Corppass Myinfo Business
// deviation from OIDC Core §5.3.2. Set only for the Myinfo Business client.
TolerateUserInfoSubjectClientID bool
}
Options is the protocol configuration for a Client: the behaviour knobs that describe how this relying party talks to its authorization server. The injectable collaborators (keys, session store, HTTP client, …) live in Dependencies. The per-product constructors in products.go fill most of this in for you.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
minimal
command
Command minimal is the smallest useful Singpass Login integration: one client, the web helper for the login/callback/logout routes, and a page that greets the signed-in user.
|
Command minimal is the smallest useful Singpass Login integration: one client, the web helper for the login/callback/logout routes, and a page that greets the signed-in user. |
|
Package keyfile holds small helpers for generating, marshaling, and loading the EC P-256 (ES256 / ECDH-ES) key pairs a relying party registers with the authorization server.
|
Package keyfile holds small helpers for generating, marshaling, and loading the EC P-256 (ES256 / ECDH-ES) key pairs a relying party registers with the authorization server. |
|
Package myinfo is the envelope-aware data model for a Myinfo / Myinfo Business /userinfo response: Response groups the data blocks, Data and Field read each item's Myinfo envelope (value or code+desc, source, classification, lastupdated, unavailable), and Authorisation flattens Corppass auth_info.
|
Package myinfo is the envelope-aware data model for a Myinfo / Myinfo Business /userinfo response: Response groups the data blocks, Data and Field read each item's Myinfo envelope (value or code+desc, source, classification, lastupdated, unavailable), and Authorisation flattens Corppass auth_info. |
|
Package web is an optional HTTP helper for building a relying-party web app on top of the singpass package.
|
Package web is an optional HTTP helper for building a relying-party web app on top of the singpass package. |