TOTP
Authen exposes APIs to create, manage and verify TOTP codes as part of a 2FA authentication flow. The flow for setting up and using TOTP is:
- Generate and display a QR code-encoded secret to the user,
- Associate the secret with the user once they have provided a verification code, and
- On every login (or other protected action) collect the user's code and verify it using the stored secret
Common Parameters
While each endpoint has its own input parameters, three common parameters worth exploring in detail: user_id
, type
and key
.
user_id
The user_id
parameter is a string, from 1 to 100 characters, which is used to identify the user. In most cases, this would by the id
of your user. From Authen's point of view, it is an opaque value.
type
The type
parameter is a string, from 1 to 100 characters, which, along with the user_id
acts as a unique identifier for a record. In most cases a user will have a single TOTP secret and thus type
can be omitted (it's always optional). But some systems use different TOTP secrets for distinct actions (e.g. login vs approving a transfer), in which case type
should be specified.
The record's primary key is actually project_id
, user_id
and type
. But the project_id
is only meaningful when running in multi-tenancy
key
The key
parameter is more complicated. Technically, it is a 32-byte key that Authen uses for encrypting and decrypting a TOTP secret. It's essentially a password for accessing the TOTP secret. When calling the APIs, it must be hex-encoded (thus, it must be exactly 64 characters). But what value should be used?
The least secure option is to use the same 32-byte value for every TOTP record. If this key is leaked and the authen database compromised, an attacker would be able to generate a valid TOTP code for all users. A more secure alternative is to derive the key from the user's password. This is reasonable since, from a data flow point of view, the password is available whenever the TOTP secret must be accessed. The downside to this approach is that the key must be updated whenever the user changes their password (see the change_key api.)
Here are examples of generating a 32-byte hex-encoded key from a string (assumed to be the user's password):
- go
- node
import (
"crypto/rand"
"encoding/hex"
"golang.org/x/crypto/scrypt"
)
func main() {
// this would be the user's password
password := []byte("Ghanima Atr3id3s")
// the salt will need to be stored along the user data
salt := make([]byte, 8)
rand.Read(salt)
key, err := scrypt.Key(password, salt, 32768, 8, 1, 32)
if err != nil {
...
}
// send encodedKey to authen as the `key` parameter
encodedKey := hex.EncodeToString(key)
}
const crypto = require('crypto');
// this would be the user's password
const password = 'Ghanima Atr3id3s';
// the salt will need to be stored along the user data
const salt = crypto.randomBytes(16);
crypto.scrypt(password, salt, 32, (err, key) => {
// send encodedKey to authen as the `key` parameter
const encodedKey = key.toString('hex');)
})
Note that these examples generate a salt
, which needs to be store (in plain text) with the user record.
Generate Secret
The first step in configuring, or changing, a TOTP secret is to generate a secret and displaying that secret to the user as a QR code:
POST /v1/totps
- Parameters
- Response
- Sample Request
- Sample Response
- Errors
name | type | req | desc |
---|---|---|---|
user_id | string [1..100] | ✓ | |
type | string [1..100] | ||
key | string [64] | ✓ | |
account | string [1..100] | ✓ | Parameter of the generated QR code to help the user identify which site the code belongs to (the account + issuer tend to appear in most TOTP client apps as a label). Authen does not store this value. |
name | type | desc |
---|---|---|
qr | string | Base64 encoded PNG of otpauth url (what the user scans). Most apps will simply return this to the client and let the client display the QR code. |
secret | string | The TOTP secret (this is encoded in the QR code ). Most apps will ignore this value. |
curl -X POST "http://127.0.0.1:5200/v1/totp" \
-H "Content-Type: application/json" \
-d '{
"user_id": "df178fd7-4f0d-4109-bd4b-ae36c11b677b",
"key": "861d97eb1edad3e8add7c88cfa9577cbe9261cc3cda7329fb632178199414b5f",
"account": "leto@test.goblgobl.com"
}'
{
"qr":"iVBORw0KGgoAAAANSUhEUgAAAQAAAAEAAQMAAABmvDolAAAABlBMVEX///8AAABVwtN+AAACqUlEQVR42uyYQY6zMAyFH8oiyxwhN4GLoULFxcJNcoQss0C8X890mHb+AxCkejMa9FWq6bP9bHzjGzcMT5IJXUGYMYKZieSqp+VGQAZ8Qo9H2CLzBk+/i4drCSD9SpapjBjtEXe/lolLewCfZYzkFjGgq12LQOnRccOUXQbIGwKAgKA0F0vTp/8UdTWgIjuBvEWfau//1ubFgEVnqgYQXcZQVYufcTEgPTCFFEjmEY4V3PUJzg0BrKjAUDorvZdoB5LbnYCMgXvYgbBwyYikfonw/H0PLQCe+isEysOzDnXgs6AdAJHJ79C7XTKwQT2tdnyepdcEYKW3lqlM2VH/+t2nwOJuBHiaYMg9bBjjhjpwrcB0/liNAPXodAtJRGnap9aAemTx5KKWIP2s9b30GgBebkC9l8ojq6Nx5f6bx/WAXrVXb5iKy2M8m9geZtwIyDKQKj0Nusijx+14vBxwE4C+ts2LzuRiqu6YcNrFFgBP6xAsMAdB+YfV2vDUFuBT2PEIM1zeTA/eRjFuBOQKUwi0TtI6XFeHgnCuDw0AZPIsMgwTNZhlL000bAfQ16695kUZbRGrqJ3s4jmaGwDkxMybP4vjki2PhD7MeLNqVwNqCLVT1wobJjL7VIE+sIw3AmR5bZ2cA/NkG5E3k/Pb5RoAMqAswmnFB9twXvtlK4CKD0cjdtT6YJ72U1GXAxLx63iIKD1Aenhbgm4B+GTbwgNTBlyWYGxaz+0APzcQPYkbKP30cuZ/DzVXAq8eJ8nMUV0OQz32so+T+MXAcZcb7E7rspPnsSzea/N6wC6cHWecx8Pd683zdoBdm6esRG1eQJ4HrQEaakt2lBur3fsBtA3gcIjFTl509KkOWh/K2BBwHJO1nGsx3+LP5P2ozdaBb3yjqfgXAAD//2lg3x8tmF2oAAAAAElFTkSuQmCC",
"secret":"5LBTYHXEBKHR4INTKRBZBSYQ4M"
}
code | desc | |
---|---|---|
102005 | The project has reached the maximum configured TOTP entries. |
The general error section details Authen's error responses as well as detailing all errors, including global errors, such as validation and internal server errors.
Verify Code
Verify the user's TOTP code. This has two modes, based on the pending
parameter. When true
, the recently generated secret (via the POST /v1/totps
endpoint), is being confirmed. When false
(the default), an already confirmed code is being verified.
Put differently, setting up a new code (or changing an existing one), is a two step process where first the secret is generated and then verified with pending: true
. Subsequent verification, say on user login, is done by calling verify with pending: false
(or omitted).
POST /v1/totps/verify
- Parameters
- Response
- Sample Request
- Errors
name | type | req | desc |
---|---|---|---|
user_id | string [1..100] | ✓ | |
type | string [1..100] | ||
key | string [64] | ✓ | |
code | string [6] | ✓ | The TOTP code. |
pending | bool | When true, confirm a recently generated secret. When false, or omitted, verify an already-confirmed secret. |
Empty. A 200
status code indicates success.
curl -X POST "http://127.0.0.1:5200/v1/totp/verify" \
-H "Content-Type: application/json" \
-d '{
"user_id": "df178fd7-4f0d-4109-bd4b-ae36c11b677b",
"key": "861d97eb1edad3e8add7c88cfa9577cbe9261cc3cda7329fb632178199414b5f",
"code": "611207"
}'
code | desc | |
---|---|---|
102006 | The TOTP entry could not be found. This means the | |
102007 | The | |
102008 | The |
The general error section details Authen's error responses as well as detailing all errors, including global errors, such as validation and internal server errors.
Delete Secret
Deletes the secret.
POST /v1/totps/delete
- Parameters
- Response
- Sample Request
- Sample Response
- Errors
name | type | req | desc |
---|---|---|---|
user_id | string [1..100] | ✓ | |
type | string [1..100] | ||
all_types | bool | When true , the type parameter is ignored and all of the user's entries are deleted. |
name | type | desc |
---|---|---|
deleted | int | The number of deleted entries. |
curl -X POST "http://127.0.0.1:5200/v1/totp/delete" \
-H "Content-Type: application/json" \
-d '{
"user_id": "df178fd7-4f0d-4109-bd4b-ae36c11b677b"
}'
{"deleted": 1}
The general error section details Authen's error responses as well as detailing all errors, including global errors, such as validation and internal server errors.
Change Key
Change the encryption key
of an entry.
While using a per-user key derived from the user's password is the most secure options, it has the obvious downside of needing to be changed whenever the user changes their password.
POST /v1/totps/change_key
- Parameters
- Response
- Sample Request
- Errors
name | type | req | desc |
---|---|---|---|
user_id | string [1..100] | ✓ | |
type | string [1..100] | ||
key | string [64] | ✓ | |
new_key | string [64] | ✓ |
Empty. A 200
status code indicates success.
curl -X POST "http://127.0.0.1:5200/v1/totp/change_key" \
-H "Content-Type: application/json" \
-d '{
"user_id": "df178fd7-4f0d-4109-bd4b-ae36c11b677b",
"key": "861d97eb1edad3e8add7c88cfa9577cbe9261cc3cda7329fb632178199414b5f",
"new_key": "4486e68dd57059570c79b146e6ae00c136ec60e65b0ee730f89795e5f73aef5e"
}'
code | desc | |
---|---|---|
102006 | The TOTP entry could not be found. This means the | |
102007 | The |
The general error section details Authen's error responses as well as detailing all errors, including global errors, such as validation and internal server errors.