UB authentication schema #

Starting from UB@5.24 UB authentication schema V2 with improved security is introduced.

It's uses algo, similar to Digest with SHA-256 as defined in [RFC 7616]. Differences from Digest SHA-256:

  • hardcoded qop="auth"
  • hardcoded algorithm="SHA-256"
  • nonceCount (nc) is not used (always=1)

UB Auth v2 #

  1. Stage1: request a nonce and realm
-> GET|POST /auth?AUTHTYPE=UB&userName=username&v=2

<- 200 OK
{ "version": 2, "nonce": "opaqueServerNonceValue", "realm": "authRealmValue", forDigestMD5: false, "connectionID": "opaque" }  
  1. Stage2: obtain session secret Client calculate random client nonce cnonce 6 chars at last. JS example:
const cnonce = Math.trunc(1e10 * Math.random()).toString(16)

and nc parameter (MUST increases for each reply of stage2 request)

let nc = 1
if (isRepeat) nc++

calculate a response using nonce and realm from stage1 + user password and nc

  HA1 = secretWord = SHA256(username.toLowerCase():realm:password)
  HA2 = SHA256('POST:auth') = 'e0ec69137e388cfad12e23c8f967754d3f20d222e2a995aa26810d1e1a0047ad' // constant for compatibility with Digest SHA-256
  response = SHA256(HA1:nonce:nc:cnonce:HA2)

result of SHA256 must be a hex16 lower-cased string

and send response to server

-> POST /auth?AUTHTYPE=UB&userName=username&v=2&s=2
Content-Type: application/json

{  
  "AUTHTYPE": "UB",
  "realm": "realmValue",
  "userName": "usernameValue",
  "cnonce": "cnonceValue",
  "nc": "ncValue"
  "response": "responseValue",
  "prefUData": {...} // optional preferred user data
}

server reply with sessionID, sessionPrivateKey and user data

<- 200 Ok

{
  "sessionID": "sessionIdValue",
  "sessionPrivateKey": "sessionPrivateKeyValue",
  "logonname": "UserName",
  "uData", "stringified user data JSON",
  "secondFactor": false // optional, if true - see https://unitybase.info/api/server-v5/tutorial-security.html#2fa  
}

or with error message

<- 500 Internal Server Error

{ "success":false, "errCode":0, "errMsg":"<<<ubErrElsInvalidUserOrPwd>>>" }

UB Auth v1 (deprecated) #

Actually this is modified DIGEST schema with SHA256 hash algorithm and modified authorization mechanism. In case of this schema usage UnityBase store client passwords hash in upasswordHashHexa attribute of uba_user entity. Schema is protected from MIT type of attack and secure enough for most type of application.

On the UI client enter a userName & password, after this client must send a two request:

1) Request a nonce #

Client call auth endpoint passing userName as parameter.

-> GET|POST /auth?AUTHTYPE=UB&userName=admin

Server return a serverNonce - one time public key valid for 5 minutes in a result field

<- 200 OK
{"result":"c0a94994a9baaf5f6f071a13c6b5f7c4f8219e1ba514d9f0b81df57cc4b4b81f"}

2) Sending hashed password and obtain a sessionWord #

Client generate clientNonce, calculate hash of his password and call auth again, passing as parameters userName, clientNonce and password hashed with nonces.

-> GET|POST /auth?AUTHTYPE=UB&
    clientNonce=ffac6401331cce72e82ecfa8dd40c8cb4456098000392da2bac8c41d19b57467&
    password=09561d07211a8ef1d125355bfb5e871028826484a30bde6c98562742d2e9460e
    &userName=admin

here:

clientNonce = unique string client generate and memorize
secretWord = sha256('salt' + passwordWhatUserEnterDuringLogin)
password=sha256('/' + serverNonce + clientNonce + userName + secretWord)

Server return sessionPrivateKey, used in future request as one of signature part.

    <- 200 OK
{
    "result":"537445910+634e82b0aa70c0ec67395d59935f1cec36c14cedc4cc824049e175109987d1c6",
    "logonname":"admin",
    "uData": {}
}

result in response is a sessionPrivateKey. First part of result before + is clientSessionID.

See UB authorization for authorization token calculation.

Consider what neither password, nor password hash not transferred other the wire, so the MIT attack is impossible.

JavaScript implementation:

var secretWord, sessionPrivateKey, hexa8ID;
promise = me.get('auth', {
    params: {
        AUTHTYPE: authSchema,
        userName: authParams.login
    }
}).then(function(resp){
    var
        serverNonce = resp.data.result,
        SHA256 = CryptoJS.SHA256;
    if (!serverNonce) throw new Error('invalid auth response');
    var clientNonce = SHA256(new Date().toISOString().substr(0, 16)).toString();
    var pwdHash = SHA256('salt' + authParams.password).toString();
    secretWord = pwdHash;
    return me.get('auth', {
        params: {
            AUTHTYPE: authSchema,
            userName: authParams.login,
            password: SHA256('/' + serverNonce + clientNonce + authParams.login + pwdHash).toString(),
            clientNonce: clientNonce
        }
    });
}).then(function(response){
    sessionPrivateKey = response.result;
    hexa8ID = hexa8(sessionWord.split('+')[0]);
});