NAME

Uniform::HTTP::Auth - HTTP authentication without an HTTP framework

SYNOPSIS

use Uniform::HTTP::Auth;

my $auth = Uniform::HTTP::Auth->new(
    origin => 'https://example.com:443',
    credentials => {
        username => 'user',
        password => 'secret',
    },
);

my $result = $auth->prepare_authentication(
    challenge_headers => [
        'Digest realm="Members", nonce="abc", qop="auth", algorithm=SHA-256',
    ],
    method         => 'GET',
    request_target => '/private',
);

my $value = $result->{value};

DESCRIPTION

Uniform::HTTP::Auth prepares HTTP authentication field values.

It supports:

  • Basic

  • Bearer

  • Digest

It does not send requests, receive 401 responses, retry requests, manage connections, or choose an HTTP framework.

The normal flow is:

server challenge
      |
      v
Uniform::HTTP::Auth
      |
      v
authentication field value
      |
      v
your HTTP client or server

The calling HTTP implementation decides whether the value belongs in Authorization or Proxy-Authorization and whether a request should be retried.

SIMPLE USERNAME AND PASSWORD

For one server, store credentials on the auth object:

my $auth = Uniform::HTTP::Auth->new(
    origin => 'https://example.com:443',
    credentials => {
        username => 'user',
        password => 'secret',
    },
);

The credentials are bound to that origin.

When a challenge arrives:

my $result = $auth->prepare_authentication(
    challenge_headers => \@www_authenticate,
    method         => 'GET',
    request_target => '/private',
);

If a supported challenge can be satisfied, $result contains:

{
    scheme    => 'digest',
    value     => 'Digest username="...", ...',
    challenge => $challenge,
}

Use $result->{value} as the complete authentication field value.

The configured default preference is:

digest
bearer
basic

You can choose another order with schemes.

BEARER TOKENS

For Bearer authentication:

my $auth = Uniform::HTTP::Auth->new(
    origin => 'https://api.example.com:443',
    credentials => {
        token => $token,
    },
);

The token is treated as opaque data. Uniform::HTTP::Auth does not obtain, refresh, decode, or validate OAuth tokens or JWTs.

USING A REQUEST OBJECT

prepare_authentication() can read the request information from a Uniform::HTTP::Request object:

my $result = $auth->prepare_authentication(
    challenge_headers => \@www_authenticate,
    request           => $request,
);

It reads method() and target().

A body is read only when has_buffered_body() is true. Authentication never drains a streaming body.

Explicit method, request_target, and entity_body arguments override values from the request object.

CONSTRUCTOR

new

my $auth = Uniform::HTTP::Auth->new(
    origin      => $origin,
    credentials => $credentials,
    schemes     => [qw(digest basic)],
);

origin

A normalized origin such as:

https://example.com:443

Static credentials require an origin and are never used for a different origin.

credentials

For Basic or Digest:

{
    username => 'user',
    password => 'secret',
}

For Bearer:

{
    token => $token,
}

A hash may contain both forms.

For applications with a credential store, credentials may instead be a callback. See "DYNAMIC CREDENTIAL LOOKUP".

schemes

An optional array reference containing enabled schemes in preference order.

The default is:

[qw(digest bearer basic)]

MAIN METHODS

prepare_authentication

my $result = $auth->prepare_authentication(
    challenge_headers => \@values,
    method            => 'GET',
    request_target    => '/private',
);

This is the main application method.

It parses the challenges, chooses a supported scheme, finds credentials, and constructs the authentication value.

It returns undef when no challenge can be satisfied.

Digest needs method and request_target. entity_body is used only for Digest qop=auth-int.

This method performs no network I/O.

parse_challenges

my $challenges = $auth->parse_challenges(@header_values);

Parses complete WWW-Authenticate or Proxy-Authenticate values.

It returns an array reference in wire order. Unknown schemes are preserved. Malformed remote challenges are returned as malformed data rather than causing an exception merely because the server sent bad input.

select

my $challenge = $auth->select($challenges);

Returns the best usable challenge according to the configured scheme order, or undef when none is usable.

This method only selects a challenge. It does not look up credentials.

schemes

Returns a new array reference containing the configured scheme names.

DYNAMIC CREDENTIAL LOOKUP

Reusable HTTP libraries and applications with a credential store can supply a callback:

my $auth = Uniform::HTTP::Auth->new(
    credentials => sub {
        my ($context) = @_;

        return $store->lookup(
            $context->{origin},
            $context->{realm},
            $context->{scheme},
        );
    },
);

The callback receives:

{
    scheme    => 'digest',
    origin    => 'https://example.com:443',
    realm     => 'Members',
    challenge => $challenge,
}

Return undef when credentials are unavailable.

For Basic and Digest return:

{
    username => 'user',
    password => 'secret',
}

For Bearer return:

{
    token => $token,
}

DIGEST SUPPORT

Digest supports:

  • MD5 and MD5-sess

  • SHA-256 and SHA-256-sess

  • SHA-512/256 and SHA-512/256-sess

  • qop=auth

  • qop=auth-int

  • UTF-8

  • userhash

  • stale nonces and nonce-count state

MD5 remains available for compatibility with older servers.

ERRORS

Programmer mistakes throw exceptions. Examples include bad constructor arguments, invalid credential values, and invalid callback results.

Bad challenge data received from a remote server is represented as malformed challenge data so callers can inspect it safely.

SECURITY

Basic authentication only encodes credentials with Base64. It should normally be used over TLS.

Bearer tokens are credentials and should be protected accordingly.

Digest is an authentication mechanism, not transport encryption. Legacy MD5 Digest is supported for interoperability but should not be preferred when a stronger option is available.

LOWER-LEVEL MODULES

Most applications should use this module.

The lower-level calculation modules are available when needed:

SEE ALSO

Uniform::HTTP, Uniform::HTTP::Request.

The detailed authentication contract is in docs/AUTH-SPEC.md.

AUTHOR

Joshua S. Day <HAX@cpan.org>

LICENSE

This software is available under the MIT License.