digest

package module
v0.1.25 Latest Latest
Warning

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

Go to latest
Published: Aug 29, 2025 License: MIT Imports: 16 Imported by: 110

README

HTTP Digest Access Authentication

go.dev reference

This package provides a http.RoundTripper implementation which re-uses digest challenges

package main

import (
	"net/http"

	"github.com/icholy/digest"
)

func main() {
	client := &http.Client{
		Transport: &digest.Transport{
			Username: "foo",
			Password: "bar",
		},
	}
	res, err := client.Get("http://localhost:8080/some_outdated_service")
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
}

Using Cookies

If you're using an http.CookieJar the digest.Transport needs a reference to it.

package main

import (
	"net/http"
	"net/http/cookiejar"

	"github.com/icholy/digest"
)

func main() {
	jar, _ := cookiejar.New(nil)
	client := &http.Client{
		Transport: &digest.Transport{
			Jar:      jar,
			Username: "foo",
			Password: "bar",
		},
	}
	res, err := client.Get("http://localhost:8080/digest_with_cookies")
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
}

Custom Authenticate Header

package main

import (
	"net/http"

	"github.com/icholy/digest"
)

func main() {
	client := &http.Client{
		Transport: &digest.Transport{
			Username: "foo",
			Password: "bar",
			FindChallenge: func(h http.Header) (*digest.Challenge, error) {
				value := h.Get("Custom-Authenticate-Header")
				if value == "" {
					return nil, digest.ErrNoChallenge
				}
				return digest.ParseChallenge(value)
			},
		},
	}
	res, err := client.Get("http://localhost:8080/non_compliant")
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
}

Override Digest Options

package main

import (
	"fmt"
	"net/http"

	"github.com/icholy/digest"
)

func main() {
	client := &http.Client{
		Transport: &digest.Transport{
			Digest: func(req *http.Request, chal *digest.Challenge, opt digest.Options) (*digest.Credentials, error) {
				switch req.URL.Hostname() {
				case "badauth.org":
					opt.Username = "foo"
					opt.Password = "bar"
				case "poorsecurity.com":
					opt.Username = "zoo"
					opt.Password = "boo"
				default:
					return nil, fmt.Errorf("unsuported host: %q", req.URL)
				}
				return digest.Digest(chal, opt)
			},
		},
	}
	res, err := client.Get("http://poorsecurity.com/legacy.php")
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
}

Low Level API

func main() {
  // get the challenge from a 401 response
  header := res.Header.Get("WWW-Authenticate")
  chal, _ := digest.ParseChallenge(header)

  // use it to create credentials for the next request
  cred, _ := digest.Digest(chal, digest.Options{
    Username: "foo",
    Password: "bar",
    Method:   req.Method,
    URI:      req.URL.RequestURI(),
    GetBody:  req.GetBody,
    Count:    1,
  })
  req.Header.Set("Authorization", cred.String())

  // if you use the same challenge again, you must increment the Count
  cred2, _ := digest.Digest(chal, digest.Options{
    Username: "foo",
    Password: "bar",
    Method:   req2.Method,
    URI:      req2.URL.RequestURI(),
    GetBody:  req2.GetBody,
    Count:    2,
  })
  req2.Header.Set("Authorization", cred.String())
}

Documentation

Index

Examples

Constants

View Source
const Prefix = "Digest "

Prefix for digest authentication headers

Variables

View Source
var ErrNoChallenge = errors.New("digest: no challenge found")

ErrNoChallenge indicates that no WWW-Authenticate headers were found.

Functions

func CanDigest

func CanDigest(c *Challenge) bool

CanDigest checks if the algorithm and qop are supported

func CutPrefix added in v0.1.25

func CutPrefix(s string) (string, bool)

CutPrefix removes the digest prefix from the header value

func IsDigest

func IsDigest(header string) bool

IsDigest returns true if the header value is a digest auth header

Types

type Challenge

type Challenge struct {
	Realm     string
	Domain    []string
	Nonce     string
	Opaque    string
	Stale     bool
	Algorithm string
	QOP       []string
	Charset   string
	Userhash  bool
}

Challenge is a challenge sent in the WWW-Authenticate header

func FindChallenge

func FindChallenge(h http.Header) (*Challenge, error)

FindChallenge returns the first supported challenge in the headers

func ParseChallenge

func ParseChallenge(s string) (*Challenge, error)

ParseChallenge parses the WWW-Authenticate header challenge

func (*Challenge) String

func (c *Challenge) String() string

String returns the foramtted header value

func (*Challenge) SupportsQOP

func (c *Challenge) SupportsQOP(qop string) bool

SupportsQOP returns true if the challenge advertises support for the provided qop value

type Credentials

type Credentials struct {
	Username  string
	Realm     string
	Nonce     string
	URI       string
	Response  string
	Algorithm string
	Cnonce    string
	Opaque    string
	QOP       string
	Nc        int
	Userhash  bool
}

Credentials is a parsed version of the Authorization header

func Digest

func Digest(chal *Challenge, o Options) (*Credentials, error)

Digest creates credentials from a challenge and request options. Note: if you want to re-use a challenge, you must increment the Count.

Example
package main

import (
	"net/http"

	"github.com/icholy/digest"
)

func main() {
	// The first request will return a 401 Unauthorized response
	req, _ := http.NewRequest(http.MethodGet, "http://httpbin.org/digest-auth/auth/foo/bar/SHA-512", nil)
	res, _ := http.DefaultClient.Do(req)
	// Create digest credentials from the request challenge
	chal, _ := digest.FindChallenge(res.Header)
	cred, _ := digest.Digest(chal, digest.Options{
		Method:   req.Method,
		URI:      req.URL.RequestURI(),
		Username: "foo",
		Password: "bar",
	})
	// Try the request again with the credentials
	req.Header.Set("Authorization", cred.String())
	res, _ = http.DefaultClient.Do(req)
	println(res.Status)
}

func ParseCredentials

func ParseCredentials(s string) (*Credentials, error)

ParseCredentials parses the Authorization header value into credentials

func (*Credentials) String

func (c *Credentials) String() string

String formats the credentials into the header format

type Options

type Options struct {
	Method   string
	URI      string
	GetBody  func() (io.ReadCloser, error)
	Count    int
	Username string
	Password string

	// The following are provided for advanced use cases where the client needs
	// to override the default digest calculation behavior. Most users should
	// leave these fields unset.
	A1     string
	Cnonce string
}

Options for creating a credentials

type Transport

type Transport struct {
	Username string
	Password string

	// Digest computes the digest credentials.
	// If nil, the Digest function is used.
	Digest func(*http.Request, *Challenge, Options) (*Credentials, error)

	// FindChallenge extracts the challenge from the request headers.
	// If nil, the FindChallenge function is used.
	FindChallenge func(http.Header) (*Challenge, error)

	// Transport specifies the mechanism by which individual
	// HTTP requests are made.
	// If nil, DefaultTransport is used.
	Transport http.RoundTripper

	// Jar specifies the cookie jar.
	//
	// The Jar is used to insert relevant cookies into every
	// outbound Request and is updated with the cookie values
	// of every inbound Response. The Jar is consulted for every
	// redirect that the Client follows.
	//
	// If Jar is nil, cookies are only sent if they are explicitly
	// set on the Request.
	Jar http.CookieJar

	// NoReuse prevents the transport from reusing challenges.
	NoReuse bool
	// contains filtered or unexported fields
}

Transport implements http.RoundTripper

Example
package main

import (
	"net/http"

	"github.com/icholy/digest"
)

func main() {
	client := http.Client{
		Transport: &digest.Transport{
			Username: "foo",
			Password: "bar",
		},
	}
	res, _ := client.Get("http://httpbin.org/digest-auth/auth/foo/bar/SHA-512")
	println(res.Status)
}

func (*Transport) CloseIdleConnections added in v0.1.21

func (t *Transport) CloseIdleConnections()

CloseIdleConnections delegates the call to the underlying transport.

func (*Transport) RoundTrip

func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip will try to authorize the request using a cached challenge. If that doesn't work and we receive a 401, we'll try again using that challenge.

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

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