/signauth

The signauth protocol

Primary LanguageJavaScriptMIT LicenseMIT

SignAuth

SignAuth is a protocol for web authentication using challenges and signatures (like Fido2) but derivating the keys from a password.

Why

When you authenticate yourself on Twitter, Facebook, Pinterest, etc. the website sends you password to the server. There, the password is derivated before being saved in the database. However, an employee could set a backdoor at that level and steal your password.

SignAuth solves entirely the problem because it uses your password locally to sign a challenge. This way, the server receive a public key and stores that.

The flow

Usage

Install it using

npm install signauth

or yarn or pnpm or whatever.

Use it with

const SignAuth = require('signauth')
const signAuth = new SignAuth()

API

Methods

signAuth.newChallenge(userid)

Creates a new challenge for the user. userid must be a not empty string.

signAuth.hashChallenge(challenge)

Hashes a challenge. This method is called internally.

signAuth.validateChallenge(challenge)

Validates a challenge

signAuth.verifySignedChallenge(challenge, signature, publicKey)

Verifies the signature of a challenge. The method is called internally

signAuth.verifyPayload(payload)

Verifies that a payload is valid

Static methods

SignAuth.getPayload(challenge, pair)

Get the payload of a challenge using the ed25519 pair

SignAuth.getPairFromPassphrase(passphrase)

Generate a seed from the passphrase and uses the seed to generate a pair of ed25519 keys

SignAuth.signChallenge(challenge, secretKey)

Signs a challenge using secretKey

Example

The code is not working code. It is to give you an idea. For real code take a look at SignAuth React/Express Boilerplate.

In the browser when the user puts userid and password you should call a first time the API to get a challenge for that user.

const SignAuth = require('signauth')

const userid = 'sullof'
const password = 'an orange sales of odd trumpets'
const passphrase = userid + password

(async function () {

    const challenge = await clientAPI('/new-challenge?userid=' + userid)
    const pair = SignAuth.getPairFromPassphrase(passphrase)
    const payload = SignAuth.getPayload(challenge, pair)
    const jwt = await clientAPI('/get-jwt-token?payload=' + JSON.stringify(payload))
    ...

})()

On the server side, you will have two api, one to return the challenge, the second to verify the payload and associate the userid to the publicKey

const SignAuth = require('signauth')
const signAuth = new SignAuth()

// login
get('/new-challenge', function (req, res) {
    let userid = req.query.userid
    let user = db.getUser(userId))
    if (user) {
        res.json({
            success: true,
            challenge: signAuth.newChallenge(userid, user.nonce)
        })
    } else {
        res.json({
            success: false,
            error: 'user not found. Please signup'
        })
    }
})

// signup
get('/first-challenge', function (req, res) {
    let userid = req.query.userid
    let user = db.getUser(userId))
    if (!user) {
        db.createUser(userid, { nonce: 1 })
        res.json({
            success: true,
            challenge: signAuth.newChallenge(userid, 1)
        })
    } else {
        res.json({
            success: false,
            error: 'user exists. Please login'
        })
    }
})

get('/get-jwt-token', function (req, res) {
    payload = JSON.parse(req.query.payload)
    if (signAuth.verifyPayload(payload)) {
        let user = db.getUser(userId))
        if (user) {
            // userid has already signed up
            if (user.publicKey !== payload.publicKey) {
                res.json(
                    success: false,
                    error: `wrong key`
                )
            } else {
                db.updateUser(userid, { nonce: nonce + 1 })
                res.json({
                    jwt: getJWTToken(userid)
                })
            }
        } else {
            res.json({
                success: false,
                error: 'user not found. Please signup'
            })
        }
    }
    res.json({
        success: false,
        error: 'wrong payload'
    })
})


History

0.1.2

  • Moving from a monorepo to a standard repo
  • Remove @signauth/crypto to use @secrez/crypto instead

0.0.5

  • Using the nonce properly

0.0.4

  • Better example in README

0.0.3

  • completing methods and tests

0.0.1

  • first version of the Crypto class is a simplified version of @secrez/core:Crypto

Test coverage

  6 passing (71ms)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |     100 |      100 |     100 |     100 |                   
 index.js |     100 |      100 |     100 |     100 |                   
----------|---------|----------|---------|---------|-------------------

Warning

SignAuth is vulnerable to phishing. A fraudolent website can pretend to be the legit one and ask you to digit username and password, and use them immediately on the right app to get access to your account. It is not different that the standard approach, but you must know that, despite the cryptography used, it is not a Fido2 that interact with the browser to be sure that it is on the right website. So, be careful.

Copyright

(c) 2020-present Francesco Sullo (francesco@sullo.co)

Licence

MIT