Skip to content

Auth Library

Andrew J. Gillis edited this page May 6, 2018 · 11 revisions

Router-side Authenticators

GoDoc

The nexus router implementation provides an authentication package that allows implementors to supply authenticator implementations. Included in this package is a challenge-response authenticator and ticket authenticator, which only need a KeyStore implementation for looking up authentication keys.

The authenticators have access to HTTP request and tracking/auth cookies when these are enabled in the router configuration. This allowing a router implementation to use cookie-based authentication, client tracking (generally for remembering previously authenticated clients), and information from websocket requests for making authentication decisions.

To enable the use of websocket request data and client tracking cookies, see the documentation for Cookie and Request Auth/Authz.

Authenticator Interface

By supplying an implementation of the Authenticator interface, any authentication logic can be defined for a router.

Nexus provides two implementations: CRAuthenticator for challenge-response authentication and TicketAuthenticatorfor ticket authentication.

CRAuthenticator

To implement a wampcra authenticator, the biggest part is to define a KeyStore. Here is a simple example that holds key information for one user:

type serverKeyStore struct {
    provider string
    role string

    pbkdf2Key []byte
    keylen int
    iterations int

    ticket string
}

func (ks *serverKeyStore) AuthKey(authid, authmethod string) ([]byte, error) {
    if authid != "jdoe" {
        return nil, errors.New("no such user: " + authid)
    }
    switch authmethod {
    case "wampcra":
        // Lookup the user's PBKDF2-derived key.                                               
        return ks.pbkdf2Key, nil
    case "ticket":
        // Lookup the user's key.                                               
        return ks.ticket, nil
    }
    return nil, errors.New("unsupported authmethod")
}

func (ks *serverKeyStore) PasswordInfo(authid string) (string, int, int) {
    if authid != "jdoe" {
        return "", 0, 0
    }
    return ks.salt, ks.keylen, ks.iterations
}

func (ks *serverKeyStore) Provider() string { return ks.provider }

func (ks *serverKeyStore) AuthRole(authid string) (string, error) {
    if authid != "jdoe" {
        return "", errors.New("no such user: " + authid)
    }
    return ks.role, nil
}

Then configure the router:

crAuth := auth.NewCRAuthenticator(&serverKeyStore{"static"}, time.Second)
config := &router.RouterConfig{
    RealmConfigs: []*router.RealmConfig{
    {
        URI:            wamp.URI("nexus.test.auth"),
        Authenticators: []auth.Authenticator{crAuth},
    },
}
nxr, err = router.NewRouter(routerConfig, nil)

The new authenticator takes care of all the rest: Creates challenge string, composes CHALLENGE message and sends to client, gets key from KeyStore, computes HMAC-SH265 over challenge string with user's key, compares computed sig to sig sent in challenge response.

A ticket authenticator is basically the same, and can use the same KeyStore instance. The KeyStore.AuthKey() can look at the authid and authmethod to determine what key to return. So that same method can return CR keys or tickets depending on authmethod.

Client-side Challenge-Response Authenticator

The nexus client library contains logic for working with challenge-response authentication. This includes any challenge-response authentication, and is not limited to the particular implementation that is included with the router auth package.

To implement a nexus client that uses WAMP CR authentication, define a function that is called when a CHALLENGE is received:

func clientAuthFunc(c *wamp.Challenge) (string, wamp.Dict) {
    // Get user password and return signature.
    password := AskUserPassoword()
    // Sign challenge with key made using password with PBKDF2.
    return crsign.RespondChallenge(password, c, nil), wamp.Dict{}
}

The function crsign.RespondChallenge is used by clients to sign the challenge string contained in the CHALLENGE message using the given password. This handles computing a derived key from the password using PBKDF2.

After defining the auth handler function, configure a client to use it:

    cfg := ClientConfig{
        Realm: "nexus.test.auth",
        HelloDetails: wamp.Dict{
            "authid": "jdoe",
        },
        AuthHandlers: map[string]AuthFunc{
            "wampcra": clientAuthFunc,
        },
        Logger: logger,
    }
    client, err := ConnectNet(routerAddress, cfg)