Basic Authentication

HTTP Basic Authentication is the simplest technique for enforcing access controls to web resources because it does not require cookies, session identifiers, or login pages; rather, HTTP Basic authentication uses standard fields in the HTTP header.

The Basic Authentication mechanism provides no confidentiality protection for the transmitted credentials. They are merely encoded with Base64 in transit, but not encrypted or hashed in any way. Therefore, basic authentication is typically used in conjunction with HTTPS to provide confidentiality.

Because the Basic Authentication field has to be sent in the header of each HTTP request, the web browser needs to cache credentials for a reasonable period of time to avoid constantly prompting the user for their username and password. Caching policy differs between browsers.

HTTP does not provide a method for a web server to instruct the client to "log out" the user. However, the Iris Basic Authentication middleware features an expiration field which you can set to re-ask for user credentials and a Context.Logout method which will force-expire the current user.

Usage

The Basic Authentication middleware is included with the Iris framework, so you do not need to install it separately.

1. Import the middleware

import "github.com/kataras/iris/v12/middleware/basicauth"

2. Configure the middleware with its Options struct:

opts := basicauth.Options{
    Allow: basicauth.AllowUsers(map[string]string{
        "username": "password",
    }),
    Realm:        "Authorization Required",
    ErrorHandler: basicauth.DefaultErrorHandler,
    // [...more options]
}

3. Initialize the middleware:

auth := basicauth.New(opts)

3.1 The above steps are the same as the Default function:

auth := basicauth.Default(map[string]string{
    "username": "password",
})

3.2 Use a custom slice of Users:

// The struct value MUST contain a Username and Passwords fields
// or GetUsername() string and GetPassword() string methods.
type User struct {
    Username string
    Password string
}

// [...]
auth := basicauth.Default([]User{...})

3.3 Load users from a file optionally, passwords are encrypted using the golang.org/x/crypto/bcrypt package:

auth := basicauth.Load("users.yml", basicauth.BCRYPT)

3.3.1 The same can be achieved using the Options (recommended):

opts := basicauth.Options{
    Allow: basicauth.AllowUsersFile("users.yml", basicauth.BCRYPT),
    Realm: basicauth.DefaultRealm,
    // [...more options]
}

auth := basicauth.New(opts)

Where the users.yml may look like that:

- username: kataras
  password: $2a$10$Irg8k8HWkDlvL0YDBKLCYee6j6zzIFTplJcvZYKA.B8/clHPZn2Ey
  # encrypted of kataras_pass
  role: admin
- username: makis
  password: $2a$10$3GXzp3J5GhHThGisbpvpZuftbmzPivDMo94XPnkTnDe7254x7sJ3O
  # encrypted of makis_pass
  role: member

4. Register the middleware:

// Register to all matched routes
// under a Party and its children.
app.Use(auth)

// OR/and register to all http error routes.
app.UseError(auth)

// OR register under a path prefix of a specific Party,
// including all http errors of this path prefix.
app.UseRouter(auth)

// OR register to a specific Route before its main handler.
app.Post("/protected", auth, routeHandler)

5. Retrieve the username & password:

func routeHandler(ctx iris.Context) {
    username, password, _ := ctx.Request().BasicAuth()
    // [...]
}

5.1 Retrieve the User value (useful when you register a slice of custom user struct at Options.AllowUsers):

func routeHandler(ctx iris.Context) {
    user := ctx.User().(*iris.SimpleUser)
    // user.Username
    // user.Password
}

Options

Here's the list of full basicauth.Options structure:

Realm directive, read http://tools.ietf.org/html/rfc2617#section-1.2 for details. E.g. "Authorization Required".

Realm string

In the case of proxies, the challenging status code is 407 (Proxy Authentication Required), the Proxy-Authenticate response header contains at least one challenge applicable to the proxy, and the Proxy-Authorization request header is used for providing the credentials to the proxy server.

Proxy should be used to gain access to a resource behind a proxy server. It authenticates the request to the proxy server, allowing it to transmit the request further.

Proxy bool

If set to true then any non-https request will immediately dropped with a 505 status code (StatusHTTPVersionNotSupported) response. Defaults to false.

HTTPSOnly bool

Allow is the only one required field for the Options type. Can be customized to validate a username and password combination and return a user object, e.g. fetch from database.

There are two available builtin values, the AllowUsers and AllowUsersFile, both of them decode a static list of users and compares with the user input (see BCRYPT function too).

Usage:

Allow: AllowUsers(iris.Map{"username": "...", "password": "...", "other_field": ...}, [BCRYPT])
// OR
Allow: AllowUsersFile("users.yml", [BCRYPT])
Allow AuthFunc

Where AuthFunc is a type of:

// Should return the user value and report whether the username & password
// match a verified user.
func(ctx iris.Context, username, password string) (interface{}, bool)

MaxAge sets expiration duration for the in-memory credentials map. By default an old map entry will be removed when the user visits a page. In order to remove old entries automatically please take a look at the GC option too.

Usage:

MaxAge: 30 * time.Minute
MaxAge time.Duration

If greater than zero then the server will send 403 forbidden status code afer MaxTries amount of sign in failures (see MaxTriesCookie). Note that the client can modify the cookie and its value, do NOT depend for any type of custom domain logic based on this field. By default the server will re-ask for credentials on invalid credentials, each time.

MaxTries int

MaxTriesCookie is the cookie name the middleware uses to store the failures amount on the client side. The lifetime of the cookie is the same as the configured MaxAge or one hour, therefore a forbidden client can request for authentication again after expiration.

You can always set custom logic on the Allow field as you have access to the current request instance.

Defaults to "basicmaxtries". The MaxTries should be set to greater than zero.

MaxTriesCookie string

If not empty then this session key will be used to store the current tries of login failures. If not a session manager was registered then the application will log an error. Note that this field has a priority over the MaxTriesCookie.

MaxTriesSession string

ErrorHandler handles the given request credentials failure. E.g when the client tried to access a protected resource with empty or invalid or expired credentials or when Allow returned false and MaxTries consumed.

Defaults to the basicauth.DefaultErrorHandler, do not modify if you don't need to.

ErrorHandler ErrorHandler

Where ErrorHandler is a type of:

func(ctx iris.Context, err error)

The err error can be one of the following types:

basicauth.ErrHTTPVersion
basicauth.ErrCredentialsForbidden
basicauth.ErrCredentialsMissing
basicauth.ErrCredentialsInvalid
basicauth.ErrCredentialsExpired

A good example of usage is the DefaultErrorHandler.

GC automatically clears old entries every x duration. Note that, by old entries we mean expired credentials therefore the MaxAge option should be already set, if it's not then all entries will be removed on "every" duration. The standard context can be used for the internal ticker cancelation, it can be nil.

Usage:

GC: basicauth.GC{Every: 2 * time.Hour}
GC GC

The GC type:

GC holds the context and the tick duration to clear expired stored credentials. See the Options.GC field.

type GC struct {
	Context context.Context
	Every   time.Duration
}

Testing

Let's learn how we can test operations like basic authentication with the Iris httptest package using its WithBasicAuth method.

1. Import the httptest subpackage:

import "github.com/kataras/iris/v12/httptest"

2. Initialize the tester object inside your test function, it requires the testing.T and iris.Application instances:

e := httptest.New(t, app)

3. Create a test case and use its WithBasicAuth method to create a request using basic authentication credentials:

e.POST("/protected").WithBasicAuth("user", "pass").
    Expect().Status(httptest.StatusOK)

Full code example can be found at: _examples/auth/basicauth/basic/main_test.go

That's all. Easy!

Last updated