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

saml

package module
v0.4.1 Latest Latest
Warning

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

Go to latest
Published: Oct 11, 2026 License: BSD-3-Clause Imports: 33 Imported by: 1

README

saml

Go Reference License CI

A SAML 2.0 service provider for an identity federation such as RENATER's Fédération Éducation-Recherche or eduGAIN. It reads the federation's signed metadata, sends people to their own university's identity provider, and reads back what that provider says about them. Pure Go, CGO_ENABLED=0.

fed := &saml.Federation{
    URL:  "https://pub.federation.renater.fr/metadata/fer/idps.xml",
    Cert: renaterCert, // metadata-signature-2026.pem, checked ONCE by fingerprint
}
if err := fed.Refresh(ctx); err != nil { ... }
go fed.Run(ctx, logError)

sp := &saml.SP{
    EntityID:   "https://app.example.org/saml",
    ACS:        "https://app.example.org/saml/acs",
    Key:        key, Cert: cert, // assertions arrive encrypted to this key
    Federation: fed,
}

url, pending, err := sp.Request(idp, relayState, saml.Options{}) // redirect there
a, err := sp.Accept(r.PostFormValue("SAMLResponse"), pending)    // at the ACS
id, from := a.Subject() // subject-id, then pairwise-id, then eppn...

It is the half of go-authn/bridge that faces the federation. The other half is an OpenID Connect provider.

What a federation sends, and what it takes to read it

Metadata One signed document listing every IdP: 343 in RENATER's fer/idps.xml, 2.2 MB. eduGAIN's is 56 MB. Valid for nine days, re-read every 45 minutes.
Signatures rsa-sha256 over exclusive canonicalisation. A Shibboleth IdP signs the Response by default, not the assertion; both are accepted, one of them has to be there.
Encryption On by default in a Shibboleth IdP. AES-128-GCM on a fresh install, AES-CBC on an older one, key transport RSA-OAEP. All of these are read.
Identifiers RENATER recommends subject-id, then pairwise-id, then eduPersonPrincipalName. eduPersonTargetedID is deprecated. Mail is not an identifier.

What it refuses

why
metadata not signed by the pinned certificate the certificate is a parameter, never fetched alongside the metadata: a key that arrives over the same channel as the document it vouches for vouches for nothing
metadata past its validUntil, or with none a signed document that never expires is a key compromise that never ends. A failed refresh keeps the last good copy, until that copy expires
metadata valid for more than 28 days from now a document valid for years is one a replay keeps alive for years. Federation.MaxValidity changes the bound; a negative value removes it
metadata older than the copy in use it is validly signed and not yet expired, and it may be the one carrying a key since revoked, served by whoever stands on the path
nothing signed, or signed with SHA-1 saml-profiles 4.1.4.5: over POST the assertion must be signed
more than one assertion the profile allows several; accepting them is how a signed one and an unsigned one arrive together and the wrong one is read (CVE-2022-41912)
anything but what the signature covered only the element rebuilt from the digested bytes is read afterwards, never the document as it arrived
a scoped identifier outside the IdP's scopes without this any of hundreds of IdPs could say it is alice@another-university.example, and everything keyed on eppn would believe it
CBC ciphertext nobody signed it is not even decrypted: unauthenticated CBC is a padding oracle ("XML Encryption is broken", 2011). RSA PKCS#1 v1.5 key transport is not accepted at all
another audience, ACS, request, an unsolicited response saml-profiles 4.1.4.3
an assertion already used a bearer assertion is whoever holds it; the replay cache keeps its ID until it expires
a condition it does not understand saml-core 2.5.1: that makes the assertion Indeterminate, not valid
MFA asked for, password returned an IdP may ignore RequestedAuthnContext; a caller that asked for a second factor must not be told it got one
a regular-expression scope the Subject Identifier profile says implementations MAY reject them, and not one IdP in RENATER uses one

Keys in metadata are trusted because metadata names them

A federation's IdP certificates are usually self-signed and often expired. That is normal: the federation's signature over the metadata is what makes the key trusted, and the certificate is only a way of carrying it. A verifier that enforced the dates would refuse a working IdP; this one does not look at them.

Verified against things this repository did not produce

  • RENATER's own metadata, as RENATER signed it on 2026-09-29, is in testdata/. Its certificate is checked against the SHA-256 fingerprint RENATER publishes, not against itself. All 343 IdPs are read, each with a signing key and a scope, and a one-byte change to one Location is refused. The clock is fixed there, because the file expires. The architecture lanes, s390x included, verify that signature too.
  • xmlsec1 signs and encrypts every response in the suite, from templates: GCM and CBC, RSA-OAEP with SHA-1 and with SHA-256/MGF1-SHA256, Response signed, assertion signed, both. The package only verifies and decrypts.
  • crewjam/saml's IdP writes whole responses to this SP's AuthnRequests, through the HTTP-Redirect and HTTP-POST bindings, from metadata scoped and signed like a federation's (interop/crewjam, a module of its own): signed with RSA-SHA256 and encrypted AES-128-CBC with the Response signed, they are accepted; its default RSA-SHA1 is refused by name; and the same CBC assertion with the Response signature removed is refused before decryption.
  • The refusals were sabotage-checked: removing the scope filter, the CBC gate, the audience check, the single-assertion rule, the SHA-1 refusal and the replay cache each turns a test red.
  • One rule could not be given a witness: reading the verified element rather than the arriving document. Every difference between the two is either caught by the digest or normalised by etree the same way canonicalisation normalises it. It stands as defence in depth, and the test that looks like its witness says so.

What a deployment adds

  • Its own metadata. sp.Metadata(saml.Description{...}) writes the SP metadata the federation registers: the entity ID, the ACS, and the key, published without a use so it serves for signing and encryption, with GCM listed before CBC so an IdP that honours the list does not fall back.
  • Discovery. saml.DiscoveryURL(service, entityID, returnURL) sends the person to the federation's discovery service, and saml.Chosen(query, fed) reads the IdP it sends back, refusing one that is not in the federation: the parameter comes through the browser, and anybody can type an entity ID.
  • ⛔ A replay cache that spans the deployment. SP.Replay defaults to NewMemoryReplay(), one process's memory. That is right for one process and wrong for several behind a load balancer: each would accept the same assertion once. A restart empties it too. Several instances implement Replay over shared storage; its Use must be atomic, so that two requests carrying the same assertion at the same moment get one true and one false.

Upgrading to v0.3.0: some identifiers change

v0.3.0 holds an unscoped identifier to the IdP that signed it (an IdP of the federation could otherwise produce another's, byte for byte). For honest IdPs, two shapes of Subject() change:

an IdP that sends before v0.3.0 from v0.3.0
an opaque string eduPersonTargetedID v v IdP!SP!v
an ePTID or persistent NameID with no NameQualifier !SPQ!v IdP!SPQ!v

Everything else (subject-id, pairwise-id, eppn, and identifiers already qualified by their own IdP) is unchanged. A stored identifier of those two shapes no longer matches: whoever keyed accounts on Subject() meets those people as new. It cannot be mapped back automatically, because the old form of the second row is the same for two IdPs that left the qualifier empty, which is the collision this release removes. A deployment that stored them maps each one to its IdP (it knows which IdP each session came from) before upgrading, or stays on v0.2.x, accepting that risk.

What it is not

No SAML IdP, no Single Logout, no artifact binding, no signed AuthnRequests (RENATER does not require them). No attribute query. No SAML 1.1: every IdP in RENATER's metadata also speaks 2.0.

Licence

BSD-3-Clause.

Documentation

Overview

Package saml is a SAML 2.0 service provider for an identity federation such as RENATER's Fédération Éducation-Recherche or eduGAIN: it reads the federation's signed metadata, sends people to their own identity provider, and reads back what that provider says about them.

fed := &saml.Federation{URL: "https://pub.federation.renater.fr/metadata/fer/idps.xml", Cert: renaterCert}
sp  := &saml.SP{EntityID: "https://app.example.org/saml", ACS: "https://app.example.org/saml/acs",
                Key: key, Cert: cert, Federation: fed}

url, pending, err := sp.Request(idp, relayState, saml.Options{})  // redirect the browser there
a, err := sp.Accept(r.PostFormValue("SAMLResponse"), pending)     // at the ACS
id, from := a.Subject()                                           // subject-id, pairwise-id, eppn...

It is the half of go-authn/bridge that faces the federation; the other half is an OpenID Connect provider. It is usable on its own by anything that wants to be a SAML SP.

Almost all of the work is refusing things, so the refusals are the package:

  • ⛔ Metadata not signed by the certificate the caller pinned, past its validUntil, or with none at all.
  • ⛔ A response in which neither the Response nor the assertion is signed, or signed with SHA-1.
  • ⛔ More than one assertion. The profile allows several; accepting them is how a signed one and an unsigned one arrive together and the wrong one is read.
  • ⛔ Anything read from the document as it arrived rather than from what the signature covered: the verified element is rebuilt from the bytes that were digested, and only it is read.
  • ⛔ An assertion for another SP, another ACS, another request, an unsolicited one, an expired one, one already used, or one whose conditions it does not understand.
  • ⛔ A scoped identifier -- eduPersonPrincipalName, subject-id, pairwise-id, eduPersonScopedAffiliation -- in a scope its IdP's metadata does not grant. Without this any of a federation's hundreds of IdPs could say it is alice@another-university.example.
  • ⛔ AES-CBC ciphertext that no verified signature covered: it is not even decrypted, because unauthenticated CBC is a padding oracle. RSA PKCS#1 v1.5 key transport is not accepted at all.
  • ⛔ An authentication context other than the one requested, when one was requested: an IdP may ignore the request, and a caller that asked for a second factor must not be told it got one.

Index

Constants

View Source
const (
	// SubjectID is the scoped, persistent, non-targeted identifier RENATER
	// recommends for research and education (saml-subject-id-attr-v1.0).
	SubjectID = "urn:oasis:names:tc:SAML:attribute:subject-id"
	// PairwiseID is the same but different for every SP, which is what
	// privacy asks for.
	PairwiseID = "urn:oasis:names:tc:SAML:attribute:pairwise-id"

	EduPersonPrincipalName     = "urn:oid:1.3.6.1.4.1.5923.1.1.1.6"
	EduPersonTargetedID        = "urn:oid:1.3.6.1.4.1.5923.1.1.1.10"
	EduPersonAffiliation       = "urn:oid:1.3.6.1.4.1.5923.1.1.1.1"
	EduPersonScopedAffiliation = "urn:oid:1.3.6.1.4.1.5923.1.1.1.9"
	EduPersonEntitlement       = "urn:oid:1.3.6.1.4.1.5923.1.1.1.7"
	EduPersonAssurance         = "urn:oid:1.3.6.1.4.1.5923.1.1.1.11"
	EduPersonOrcid             = "urn:oid:1.3.6.1.4.1.5923.1.1.1.16"
	Mail                       = "urn:oid:0.9.2342.19200300.100.1.3"
	DisplayName                = "urn:oid:2.16.840.1.113730.3.1.241"
	GivenName                  = "urn:oid:2.5.4.42"
	Surname                    = "urn:oid:2.5.4.4"
	CommonName                 = "urn:oid:2.5.4.3"
	UID                        = "urn:oid:0.9.2342.19200300.100.1.1"
	SchacHomeOrganization      = "urn:oid:1.3.6.1.4.1.25178.1.2.9"
	IsMemberOf                 = "urn:oid:1.3.6.1.4.1.5923.1.5.1.1"
	// SupannEtablissement is the French SupAnn schema's establishment code.
	// RENATER says not to depend on SupAnn for eduGAIN users.
	SupannEtablissement = "urn:oid:1.3.6.1.4.1.7135.1.2.1.14"
)

Attribute names, as a federation's IdPs send them: the urn:oid form (NameFormat uri), which is what RENATER and eduGAIN use.

Variables

This section is empty.

Functions

func DiscoveryURL

func DiscoveryURL(service, spEntityID, returnURL string) (string, error)

DiscoveryURL sends somebody to a discovery service to choose their IdP, per the SAML IdP Discovery Protocol (sstc-saml-idp-discovery, 2.4.1): entityID is this SP, and the service sends them back to returnURL with the chosen IdP in the "entityID" parameter.

saml.DiscoveryURL("https://discovery.renater.fr/renater", sp.EntityID, back)

RENATER's service lists every IdP of the federation and cannot be restricted; an SP that trusts fewer runs its own page instead, from Metadata.Sorted.

Types

type Assertion

type Assertion struct {
	// IdP is who said it.
	IdP *IdP

	ID     string
	NameID NameID

	AuthnInstant time.Time
	// AuthnContext is the class the IdP authenticated with, e.g.
	// "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
	// or "https://refeds.org/profile/mfa".
	AuthnContext        string
	SessionIndex        string
	SessionNotOnOrAfter time.Time
	NotOnOrAfter        time.Time

	// Attributes by name (the urn:oid form), with scoped values the IdP was
	// not allowed to assert already removed.
	Attributes map[string][]string
}

An Assertion is what an IdP said about somebody, after every check.

func (*Assertion) First

func (a *Assertion) First(name string) string

First is the first value of an attribute, or "".

func (*Assertion) Subject

func (a *Assertion) Subject() (value, from string)

Subject is the best persistent identifier the IdP released, and which one it was -- in RENATER's order of preference: subject-id, then pairwise-id, then eduPersonPrincipalName, then the (deprecated) eduPersonTargetedID, then a persistent NameID. It is "" when the IdP released none of them, which leaves nothing that identifies the same person twice.

⛔ Mail is never an identifier: it is reassigned and, RENATER notes, can be spoofed.

Scoped identifiers compare case-insensitively (saml-subject-id-attr-v1.0 3.3.1), so they are returned lower-cased.

type Description

type Description struct {
	// Names and Descriptions by language: "fr" is required by RENATER, "en"
	// by eduGAIN.
	Names        map[string]string
	Descriptions map[string]string

	InformationURL   string
	PrivacyStatement string

	// Discovery is where a discovery service may send people back to with
	// the IdP they chose (SAML IdP Discovery Protocol 2.4.1). A discovery
	// service SHOULD refuse to return anywhere not listed here.
	Discovery string

	// Technical is the technical contact's e-mail address -- a person, per
	// RENATER, not a list.
	Technical string

	// Attributes are the attributes the SP asks for. RENATER wants each one
	// justified at registration; the list here is what goes in the metadata.
	Attributes []string
}

Description is what a federation registry asks about an SP. RENATER's technical framework makes a French name and description, a technical contact and (for the Code of Conduct) a privacy statement obligatory.

type Federation

type Federation struct {
	// URL is where the metadata is: https://, or file:// for a copy kept by
	// something else. Plain http is refused except on loopback, not
	// because the signature would not catch a change -- it would -- but
	// because somebody on the path could hold back the NEW document and
	// keep serving an old one with a revoked key until it expires.
	URL string

	// Cert is the federation's metadata signing certificate, checked once by
	// its fingerprint and kept locally.
	Cert *x509.Certificate

	// Client fetches it. A client with a two-minute timeout when
	// nil: eduGAIN's aggregate is over 50 MB.
	Client *http.Client

	// MaxSize bounds a download. 256 MB by default.
	MaxSize int64

	// MaxValidity is the longest validUntil a document may carry, from now:
	// 28 days by default (RENATER publishes about two weeks, eduGAIN five
	// days); a negative value removes the bound. A document valid for years
	// is one a replay keeps alive for years.
	MaxValidity time.Duration

	// Now is the clock. time.Now by default.
	Now func() time.Time
	// contains filtered or unexported fields
}

A Federation keeps a federation's metadata fresh: it fetches it, verifies it, and fetches it again before it goes stale.

f := &saml.Federation{
    URL:  "https://pub.federation.renater.fr/metadata/fer/idps.xml",
    Cert: renaterCert,
}
if err := f.Refresh(ctx); err != nil { ... }
go f.Run(ctx, func(err error) { log.Print(err) })

When a refresh fails, the last good copy stays in use -- a federation server being briefly unreachable must not log everybody out -- but only until its own validUntil. After that there is nothing left that anybody vouches for, and every IdP disappears.

func (*Federation) IdP

func (f *Federation) IdP(entityID string) (*IdP, bool)

IdP implements IdPs, from the current metadata if it is still valid.

func (*Federation) Metadata

func (f *Federation) Metadata() *Metadata

Metadata is the current metadata, or nil when there is none that is still valid.

func (*Federation) Refresh

func (f *Federation) Refresh(ctx context.Context) error

Refresh fetches and verifies the metadata once. A document that fails verification does not replace the one in use.

func (*Federation) Run

func (f *Federation) Run(ctx context.Context, errs func(error))

Run refreshes the metadata until ctx ends: at once, then at three quarters of its cacheDuration, which is what Shibboleth does -- never more often than every five minutes nor less often than every twelve hours, and never past half of what remains of its validity. With no valid metadata it tries again within a minute, then backs off to an hour: a federation server down at start must not leave the SP without IdPs for twelve hours. errs, when not nil, is told about every failed refresh.

type IdP

type IdP struct {
	// EntityID is the IdP's name in the federation, e.g.
	// "https://idp.univ-example.example/idp/shibboleth".
	EntityID string

	// SSO is the SingleSignOnService location for the HTTP-Redirect binding,
	// which is where an AuthnRequest goes.
	SSO string

	// Keys are the certificates it signs with. A federation's metadata is
	// what makes them trusted; see verify.
	Keys []*x509.Certificate

	// Scopes are the scopes it may assert scoped values in -- the part after
	// the "@" of eduPersonPrincipalName, subject-id, pairwise-id and
	// eduPersonScopedAffiliation. A value in any other scope is dropped.
	Scopes []string

	// Names are its display names by language ("fr", "en"), from mdui, or
	// failing that from the Organization element. The discovery page shows
	// them.
	Names map[string]string

	// Categories are its entity categories and assurance certifications,
	// e.g. "http://refeds.org/category/research-and-scholarship" or
	// "https://refeds.org/sirtfi".
	Categories []string

	// Domains are the mdui:DomainHint values, which let a discovery page
	// match somebody typing their e-mail domain.
	Domains []string
}

An IdP is one identity provider as its federation describes it: where to send people, which keys sign what comes back, and which scopes it may speak for.

func Chosen

func Chosen(q url.Values, federation IdPs) (*IdP, error)

Chosen reads the IdP a discovery service sent back, and refuses one that is not in the federation: the parameter comes through the browser, and anybody can type an entity ID.

func (*IdP) Has

func (i *IdP) Has(category string) bool

Has says whether the IdP carries an entity category or certification.

func (*IdP) Name

func (i *IdP) Name(langs ...string) string

Name is the IdP's display name in the first of langs that it has, then in English, then in any language, then its entity ID.

type IdPs

type IdPs interface {
	IdP(entityID string) (*IdP, bool)
}

IdPs is where an SP looks identity providers up: a *Metadata, or a *Federation that keeps one fresh.

type MemoryReplay

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

MemoryReplay is a Replay for one process.

func NewMemoryReplay

func NewMemoryReplay() *MemoryReplay

NewMemoryReplay returns an empty MemoryReplay.

func (*MemoryReplay) Use

func (m *MemoryReplay) Use(id string, expires time.Time) bool

Use implements Replay. Expired entries are dropped as it goes, so the set is bounded by how many assertions arrive in one validity window.

type Metadata

type Metadata struct {
	// IdPs by entity ID. Only IdPs that speak SAML 2.0 and publish an
	// HTTP-Redirect SingleSignOnService are here; the others could not be
	// sent anybody.
	IdPs map[string]*IdP

	// Skipped says what was left out, and why: an entity whose signing
	// certificates none parse, one described twice, a certificate that does
	// not. ⛔ One member's mistake must not take the federation's other
	// IdPs away -- it used to: a single certificate Go refuses (a negative
	// serial, since Go 1.23) failed the whole document.
	Skipped []string

	// ValidUntil is when the signer stops vouching for this document. After
	// it, the document is refused, and a server still holding it should
	// stop trusting it rather than keep going on stale keys.
	ValidUntil time.Time

	// CacheDuration is how long the signer suggests keeping it before
	// asking again. Zero when the document does not say.
	CacheDuration time.Duration
	// contains filtered or unexported fields
}

Metadata is a federation's signed list of identity providers.

func ParseMetadata

func ParseMetadata(data []byte, cert *x509.Certificate, now time.Time) (*Metadata, error)

ParseMetadata verifies a federation's metadata against the certificate its operator publishes, and reads the identity providers out of it.

cert := ... // RENATER's metadata-signature-2026.pem, checked by fingerprint ONCE
md, err := saml.ParseMetadata(body, cert, time.Now())

� The certificate is a parameter, never something fetched alongside the metadata: a key that arrives over the same channel as the document it vouches for vouches for nothing. RENATER says the same -- download it once, check its fingerprint, keep a local copy.

Refused:

  • a document that is not signed, or not signed by cert, or signed with SHA-1;
  • a document past its validUntil, or with none at all -- a signed document that never expires is a key compromise that never ends;
  • a document whose signature covers a different element than the root.

func (*Metadata) IdP

func (m *Metadata) IdP(entityID string) (*IdP, bool)

IdP returns the identity provider with this entity ID.

func (*Metadata) Sorted

func (m *Metadata) Sorted(langs ...string) []*IdP

Sorted returns the IdPs ordered by display name in the first of langs, the order a discovery page lists them in.

type NameID

type NameID struct {
	Format, NameQualifier, SPNameQualifier, Value string
}

A NameID is the subject's name as the IdP chose to write it. In a research federation it is usually transient -- a new value every time -- and the person is identified by an attribute instead.

type Options

type Options struct {
	// ForceAuthn asks the IdP to authenticate the person again even if it
	// has a session for them (OIDC prompt=login).
	ForceAuthn bool

	// IsPassive asks the IdP not to interact with the person at all (OIDC
	// prompt=none). An IdP with no session answers NoPassive.
	IsPassive bool

	// AuthnContext lists the authentication context classes that are
	// acceptable, e.g. "https://refeds.org/profile/mfa". When set, a
	// response whose class is not one of them is REFUSED: an IdP may ignore
	// the request, and a caller that asked for a second factor must not be
	// told it got one when it did not.
	AuthnContext []string
}

Options change what is asked of the IdP.

type Pending

type Pending struct {
	ID           string
	IdP          string
	Issued       time.Time
	AuthnContext []string
	// ForceAuthn is whether the request asked the IdP to authenticate the
	// person again rather than reuse its session: then an AuthnInstant
	// from before the request is refused, not believed.
	ForceAuthn bool
}

Pending is a request sent and not yet answered. The caller keeps it -- in the session, keyed by the RelayState -- and hands it back to Accept.

type Replay

type Replay interface {
	// Use records id until expires, and says whether it was new. It must be
	// atomic: two requests with the same assertion at the same moment get
	// one true and one false.
	Use(id string, expires time.Time) bool
}

Replay remembers which assertions have been used.

saml-profiles 4.1.4.5: the SP "MUST ensure that bearer assertions are not replayed, by maintaining the set of used ID values for the length of time for which the assertion would be considered valid". A bearer assertion is exactly that -- whoever holds it is the person -- and a browser's history, a proxy's log or a shoulder is enough to hold one.

type SP

type SP struct {
	// EntityID is this SP's name in the federation, as registered.
	EntityID string

	// ACS is the AssertionConsumerService URL, where IdPs POST responses.
	// It is compared exactly with the Destination and Recipient the IdP
	// writes, so it must be spelled as it is in the SP's metadata.
	ACS string

	// Key decrypts assertions and Cert is its certificate, which goes in
	// the SP's metadata. A Shibboleth IdP encrypts assertions by default,
	// so an SP without a key cannot read most of a federation.
	Key  *rsa.PrivateKey
	Cert *x509.Certificate

	// Federation is where IdPs are looked up.
	Federation IdPs

	// Replay remembers the assertions already used. The default is in
	// memory, which is right for one process and wrong for several behind
	// a load balancer: each would accept the same assertion once.
	Replay Replay

	// ClockSkew is how far the IdP's clock may be from this one. Three
	// minutes by default.
	ClockSkew time.Duration

	// Now is the clock. time.Now by default.
	Now func() time.Time
	// contains filtered or unexported fields
}

An SP is a SAML 2.0 service provider: it sends people to their identity provider and reads what comes back.

func (*SP) Accept

func (sp *SP) Accept(samlResponse string, p Pending) (*Assertion, error)

Accept reads the SAMLResponse form field an IdP POSTed to the ACS, as the answer to p, and returns the assertion in it.

Everything that is refused is one error to the caller; the error text is for the server's log, not for the browser.

func (*SP) Metadata

func (sp *SP) Metadata(d Description) ([]byte, error)

Metadata is the SP's own metadata, to register with the federation.

The key is published without a "use", so it serves for both signing and encryption, and with the encryption methods it prefers: GCM first, so that an IdP that honours the list does not fall back to CBC.

func (*SP) Request

func (sp *SP) Request(idp *IdP, relayState string, o Options) (string, Pending, error)

Request builds the URL that sends somebody to idp with an AuthnRequest, over the HTTP-Redirect binding.

relayState comes back unchanged with the response; SAML bindings (3.4.3) limit it to 80 bytes, and it is refused beyond that rather than silently truncated by an IdP. Nothing secret belongs in it: it travels through the browser in the clear.

type StatusError

type StatusError struct {
	Code, Sub, Message string
}

A StatusError is an IdP saying no: the person could not be authenticated, or would have had to interact when IsPassive forbade it. It is not an attack and is worth telling apart from one -- NoPassive is how OIDC's prompt=none learns to answer login_required.

func (*StatusError) Error

func (e *StatusError) Error() string

func (*StatusError) NoPassive

func (e *StatusError) NoPassive() bool

NoPassive says whether the IdP refused because IsPassive was set and it would have had to ask the person something.

Jump to

Keyboard shortcuts

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