Multipass
Better authentication for HTTP
Multipass is like HTTP Basic authentication but better and without passwords.
Multipass implements the idea to authenticate users based on something they own instead of something they know. This is better known as the second factor of Two-factor Authentication.
Multipass comes in two forms; a single binary to run in front of your web services and as a package to include in your Go project.
Installation
Download the binary from the releases page or build from source.
Usage
$ multipass -conf multipass.conf
For an example configuration see Configuration.
Contribution
Bug reports and feature requests are welcome. Follow GiHub's guide to using-pull-requests.
Goal
Protect internet exposed web resources and services with automatic HTTPS (TLS) and provide user friendly authentication.
Motivation
Many private web resources and services end up exposed on the internet, accessible by anyone. Think IP video cameras, Key-value stores, analytic applications and many more. Using Multipass, these web resources and services can be protected using automatic HTTPS (TLS) and access can be granted on an individual basis.
Further reading
Build
The Multipass binary depends on the excellent Caddy webserver.
-
Get the Caddy web server source code:
$ go get github.com/mholt/caddy
-
Register Multipass as a caddy plugin by adding multipass to the caddy directive:
Open $GOPATH/src/github.com/mholt/caddy/caddyhttp/httpserver/plugin.go
in your favorite editor and make the following changes.
var directives = []string{
...
"expvar",
"multipass",
"proxy",
...
}
-
Get the Multipass source code and build the command:
$ go get github.com/namsral/multipass
$ go install github.com/namsral/multipass/cmd/multipass
The next thing is to create a configuration file and run the multipass command.
Configuration
Syntax
Use the following syntax:
multipass {
resources path [path]
handles email [email]
basepath path
expires duration
smtp_addr host:port
smtp_user username
smtp_pass password
smtp_client command [args...]
mail_from email
mail_tmpl path
}
- resources: path of resources to protect. Default: /
- handles: the handles which identify the users; accepts wildcards like '@' and '@dallas'. Required
- basepath: path to the log-in and sign-out page. Default: /multipass
- expires: The time duration after which the token expires. Any time duration Go can parse. Default: 24h
- smtp_addr: Mailserver address used for sending login links. Default: localhost:25
- smtp_user: Mailserver username used for authentication.
- smtp_pass: Mailserver password used for authentication.
- smtp_client: SMTP client command with arguments. Mutually exclusive with smtp_addr
- mail_from: From address used in email messages sent to users. Required
- mail_tmpl: Path to mail template to override deault subject, plain and html body.
Examples:
In the following example, the service running on localhost:2016
is proxied and protected to allow only users with handles leeloo@dallas and korben@dallas to access the /fhloston
and /paradise
resources.
example.com {
bind 0.0.0.0
multipass {
resources /fhloston /paradise
handles leeloo@dallas korben@dallas
basepath /multipass
expires 24h
smtp_addr localhost:2525
mail_from "Multipass <no-reply@dallas>"
}
proxy / localhost:2016
log stdout
}
Same example but replaced the SMTP server with a SMTP client and accepts a domain wildcard:
example.com {
bind 0.0.0.0
multipass {
resources /fhloston /paradise
handles @dallas
basepath /multipass
expires 24h
smtp_client /usr/sbin/sendmail -t -i
mail_from "Multipass <no-reply@dallas>"
}
proxy / localhost:2016
log stdout
}
How it works
Multipass works by sending the user a login link with an embedded access token. When the user follows the login link the access token is stored in the browser session and used to authenticate the user on successive requests. The access token is a JSON web token containing claims specific to Multipass and signed with a RSA key pair.
User flow:
- User visits protected resource
- User is redirected to log-in page and enters a known handle, e.g. email address
- An user access token is sent to user in the form of a login link
- User follows the login link and is granted access the protected resource
JWT
Access tokens are signed JSON Web Tokens with specific claims like user handle and expire date. The tokens are embedded in login links which are sent to user.
RSA key pairs
A RSA key pair is used to sign user access tokens. These access tokens and other signatures can be verified by others using the public key made available at the url [siteaddr][basepath]/pub.cer
when Multipass is running.
You can set your own private RSA key in the MULTIPASS_RSA_PRIVATE_KEY
environment variable; make sure to PEM encode the private key.
When no private key is set, the MULTIPASS_RSA_PRIVATE_KEY
environment variable is empty, a RSA key pair is randomly generated and stored in the environment. This ensures signatures still validate after Multipass reloads during a configuration reload.
Automatic HTTPS
Multipass piggybacks on the Caddy web server which comes with automatic HTTPS using Let's Encrypt and many more features and plugins.
Reverse Proxy
The user handle which was used to authenticate the user is passed down to the protected web services as a HTTP header:
Multipass-Handle: <user handle>
Include in Go project
Multipass comes with multipass.AuthHandler
which can wrap any http.Handler to provide Multipass authentication. Handlers from other routers and frameworks can be supported, see the caddy sub-package for an example.
In the example below, the appHandler function is wrapped using the AuthHandler
wrapper. It assumes you have a SMTP service running on localhost:2525
and
a user identified by email address leeloo@dallas whom has access to the resource at
/private
.
package main
import (
"fmt"
"log"
"net/http"
"github.com/namsral/multipass"
"github.com/namsral/multipass/services/email"
)
func appHandler(w http.ResponseWriter, r *http.Request) {
handle := r.Header.Get("Multipass-Handle")
if len(handle) == 0 {
handle = "anonymous"
}
switch r.URL.Path {
case "/", "/private":
fmt.Fprintf(w, "Hello %s, welcome to %s", handle, r.URL.Path)
return
}
http.NotFound(w, r)
}
func main() {
service, err := email.NewUserService(email.Options{
SMTPAddr: "localhost:2525",
FromAddr: "Multipass Bot <noreply@dallas>",
})
if err != nil {
log.Fatal(err)
}
service.AddHandle("leeloo@dallas")
service.AddResource("/private")
addr := "localhost:6080"
siteaddr := "http://" + addr
m := multipass.New(siteaddr, multipass.Service(service))
h := multipass.AuthHandler(http.HandlerFunc(appHandler), m)
log.Fatal(http.ListenAndServe(addr, h))
}
Extending
Extending Multipass by implementing the UserService interface.
By implementing the UserService, shown below, Multipass can be extended to support other user handles which can identify and other ways to notify users of requested login URLs.
type UserService interface {
Listed(handle string) bool
Authorized(handle, method, rawurl string) bool
Notify(handle, loginurl string) error
Close() error
}