Authen

Authen is a web service used to enhance an existing authentication system. It is a Go executable that exposes an HTTP interface and supports 3 storage backends: SQLite, PostgreSQL and CockroachDB.

It is open source, easy to install, reliable, secure and performant. When the PostgreSQL or CockroachDB storage options are used, multiple instances of Authen can be run to achieve high available and horizontal scaling.

Running

Authen is meant to be both easy to run and easy to compile.

You can grab the latest pre-compiled binary, create a simple config.json and run it.

To compile the project, you'll need a recent version of Go (1.18+). Download the source and run:

wget https://github.com/goblgobl/authen/archive/refs/heads/master.tar.gz
tar -xzvf master.tar.gz
cd authen-master
make build

To generate an executable named authen.

Authentication

Authen is designed to be called exclusively from a controlled system (i.e. a backend system). It should not be exposed publicly. As such, it does not authenticate requests.

Authen does support multi-tenancy though. See the multi-tenancy section.

Configuration

By default, the configuration will be loaded from the config.json file. This can be changed by specifying the -config PATH argument on startup.

{
"storage": {
"type": "sqlite",
"path": "/opt/authen/data.sqlite"
},
"totp": {
"issuer": "yourcompany.com"
}
}
{
"instance_id": 0,
"migrations": true,
"db_clean_frequency": 120,
"project_update_frequency": 120,

"log": {
"level": "info",
"requests": true,
"pool_size": 100,
"format": "kv",
"kv": {
"max_size": 4096
}
},

"http": {
"listen": "127.0.0.1:5200"
},

"storage": {
"type": "postgres",
"url": "postgres://localhost:5432/gobl_authen"
},

"totp": {
"max": 0,
"setup_ttl": 300,
"secret_length": 16,
"issuer": "yourcompany.com"
},

"ticket": {
"max": 0,
"max_payload_length": 0
},

"login_log": {
"max": 0,
"max_payload_length": 0
}
}

Common Configuration Settings

log.level

The log level to use. Can one one of INFO, WARN, ERROR, FATAL or NONE. Defaults to INFO.

log.requests

HTTP requests are logged regardless of the configured log.level. Set this value to false if you do not want HTTP requests to be logged. Defaults to true, which will log HTTP requests.

http.listen

The address:port that the HTTP should listen on. Please note that Authen is not designed to be exposed publicly. The specified address should not be publicly reachable. Defaults to 127.0.0.1:5200.

totp.issuer

The issuer to include in the generated otpauth URL (which is part of the data encoded in the QR code). This helps prevent collisions when the user has multiple TOTP for different services. This will generally be the name of your company or website.

storage.type

Must be either sqlite, postgres or cockroach.

This setting is required and there is no default.

storage.sqlite.path

When storage.type is set to sqlite, this must be set to the location of the SQLite database. If the file does not exist, it will be created.

storage.postgres.url

When storage.type is set to postgres, this must be the postgres connection string.

storage.cockroach.url

When storage.type is set to cockroach, this must be the cockroach connection string. Note that cockroach DB uses the same connection string format as PostgreSQL, including the postgres:// protocol. Typically, only the port is different.

Advanced Configuration Settings

instance_id

This value should only be set when multiple instances of Authen are deployed. It defaults to 0, which is fine when a single instance is used.

Every request is assigned a RequestId, which is included in any logs generated with respect to the request, as well as placed in the RequestId header of the response. While the RequestId isn't guaranteed to be unique, in deployments using multiple instances, giving each instance a unique id (from 0-255) will greatly reduce duplicates.

multi_tenancy

See the multi-tenancy section for more details. In short, when true, each tenant (called a project) is loaded from the database based on the Project request header. Some of the configuration settings listed here are ignored in favor of project-specific configuration (stored along the project in the database).

project_update_frequency

Frequency, in seconds, to scan for changed project configuration. This value should only be set when multi-tenancy is enabled. It defaults to 0, which disables the scan, which is correct in single-tenancy as there is no project data.

A reasonable value for this, when using multi-tenancy, would be 60. This would mean that changes made to existing projects would take up to 60 seconds before being registered.

db_clean_frequency

Frequency, in seconds, to "clean" the database. Exactly what happens when the database is "cleaned" is meant to be an implementation detail. The behavior can change in the future. It currently means:

The default value is 120 seconds. This value should not be set to 0, which disables the cleaner, except for advance cases. When multiple instances are used (HA), it is reasonable to disable the cleaner on all but one instance. Alternatively, in most cases, letting the cleaner run on each instance is also fine (the cleaner start with a random delay, so each instance will likely run at different times).

migrations

By default, Authen will automatically run data migrations on startup (these are baked into the binary). The only valid reason to set this to false is when multiple Authen servers are deployed. See the migrations section for more details.

log.pool_size

Authen pre-allocates a pool of loggers which helps reduce the amount of memory that is created and which must be garbage collected during runtime. The amount of pre-allocated memory depends on the pool_size and the configured maximum log size.

For best performance, at the cost of memory, this should be set to the maximum number of concurrent requests the system will handle. It defaults to 100.

The log pool will not grow or shrink and is non-blocking. If more loggers are requested than the pool can handle, loggers will be dynamically allocated, but will not be added back to the pool.

log.format

The format of the generated log messages. Currently, the only supported value, and the default, is kv for a key=value type log output.

log.kv.max_size

The maximum size of an individual log message. Any additional data will be discarded. Defaults to 4096. With a default pool_size of 100, the total memory pre-allocated for logging is 100 * 4096 bytes (0.4096 megabytes).

totp.max

The maximum number of TOTP entries to store. The default, 0 allows for an unlimited number. For self-hosted setups where you have total control over the backend which calls Authen, there's generally no reason to set this.

In multi-tenancy mode, this value is ignored and the project-specific configuration is used.

totp.setup_ttl

A user's TOTP is configured, or changed, in two phases. First the user is presented with a QR code and then their TOTP is validated. Having the user validate the code ensures that we don't enable a TOTP secret that the user cannot verify. This setting defines the time, in second, that the user has for entering their verification from the time the QR code is generated. Defaults to 300 (5 minutes).

In multi-tenancy mode, this value is ignored and the project-specific configuration is used.

totp.secret_length

The length of the TOTP secret to generate. Defaults to 16.

In multi-tenancy mode, this value is ignored and the project-specific configuration is used.

ticket.max

The maximum number of tickets that can exist at once. The default, 0 allows for an unlimited number. For self-hosted setups where you have total control over the backend which calls Authen, there's generally no reason to set this.

In multi-tenancy mode, this value is ignored and the project-specific configuration is used.

ticket.max_payload_length

The maximum length, in bytes, of each ticket payload. The default, 0 allows for an unlimited payload length. For self-hosted setups where you have total control over the backend which calls Authen, there's generally no reason to set this.

In multi-tenancy mode, this value is ignored and the project-specific configuration is used.

login_log.max

The maximum number of login_logs allowed to be created. The default, 0 allows for an unlimited number. For self-hosted setups where you have total control over the backend which calls Authen, there's generally no reason to set this.

In multi-tenancy mode, this value is ignored and the project-specific configuration is used.

login_log.max_payload_length

The maximum length, in bytes, of each login log payload. The default, 0 allows for an unlimited payload length. For self-hosted setups where you have total control over the backend which calls Authen, there's generally no reason to set this.

In multi-tenancy mode, this value is ignored and the project-specific configuration is used.

Errors and Codes

Authen tries to provide developer-friendly error and validation messages. Every error response has an integer code field which identifies the error. Every error response also has a string error field which is a description of the error, in English. While basic and aimed at guiding developers, the error field will never contain sensitive data and can be shown to end-users (although, again, it's rather basic, might be a little technical, and is always in English).

For example, a request to an invalid route would return a response with a 404 status code, as well as body with a code and error field:

$ curl http://127.0.0.1:5200/invalid

{
  "code": 102001,
  "error": "not found"
}

Error Codes

codedesc
2001

A generic internal server error. This is the least specific and thus least useful error. The response will have an uuid Error-Id header and the same value will be in the error_id field of the response. Assuming ERROR level (or lower) logging is enabled, a log containing the eid=$ID attribute will contain more data. Because this is an unexpected error, including more details in the HTTP response could result in sensitive data being leaked, thus only a referenced to the logged error is provided.

2002

A response could not be serialized to JSON. Like the 2001 error, please see the error_id and corresponding log entry. This error is almost certainly a result of a bug. We we hope that you'll report it.

2003

The request payload was not valid JSON.

2004

The request contained invalid data. See the validation section for details on validation errors.

102001

An http 404 that secifically relates to the URL path being unknown (as opposed to, say, an endpoint returning a 404 because some ID wasn't valid).

102002

Project header is missing. Only applicable when multi-tenancy is enabled.

102003

The id specified by the Project header was not valid. Only applicable when multi-tenancy is enabled.

102005

The project has reached the maximum configured TOTP entries.

102006

The TOTP entry could not be found. This means the user_id or optionally user_id + type did not correspond to an existing TOTP (or project_id + user_id + type when multi-tenancy is enabled). Note that TOTP that are setup but not confirmed before the configured TTL are periodically deleted.

102007

The key parameter to decrypt/encrypt the TOTP secret was incorrect.

102008

The code parameter to confirm or verify a TOTP was incorrect.

102009

The project has reached the maximum configured tickets.

102010

The payload is larger than the maximum configured length.

102011

The ticket could not be found.

102012

The project has reached the maximum configured login logs.

102013

The payload is larger than the maximum configured length.

Validation Errors

A validation error is a normal error with a code of 2004. Validation errors always contain an invalid field which contains an error of errors. Each item within this array contains its own code and error field and, optionally, a field and data field.

$ curl -X POST "http://127.0.0.1:5200/v1/totp" \
   -H "Content-Type: application/json" \
   -d '{"key": "goku", "account": 9001}'

{
"code": 2004,
"error": "invalid data",
"invalid": [
  {
    "code": 1003,
    "field": "key",
    "error": "must be between 64 and 64 characters",
    "data": {"min": 64, "max": 64}
  },
  {
    "code": 101001,
    "field": "key",
    "error": "key must be a 32-byte hex encoded value"
  },
  {
    "code": 1001,
    "field": "user_id",
    "error": "required"
  },
  {
    "code": 1002,
    "field": "account"
    "error": "must be a string",
  }
]}
codedesc
1001

The value is required.

1002

The value must be a string.

1003

The string must be within a certain min and max length.

1004

The string does not satisfy the required pattern.

1005

The value must be an integer.

1006

The integer must be greater or equal than min.

1007

The integer must be less than or equal than max.

1008

The integer must be greater or equal than min and less than or equal than max.

1009

The value must be a boolean.

1010

The value must be a uuid.

101001

The key parameter was not a valid HEX-encoded value.

101002

The ticket parameter was not a valid Base64-encoded value.

The error field contains a user-friendly error message in English. The presence and contents of the optional data field depends on the code. We can see that code 1003, which represents a string length validation error, contains a data field with the min and max length.

Multi-Tenancy

Authen supports multi-tenancy largely by way of project_id uuid columns in the database. When multi-tenancy is enabled, the project is identified via the Project header.

To enable multi-tenancy, the multi_tenancy configuration parameter must be set to true.

Even with multi-tenancy enabled, Authen does not do authentication. The Project header parameter becomes required, and an invalid or unknown id will result in an error, but it is up to the caller to make sure that the correct id is passed.

Project Management

There is currently no API to manage projects. However, projects can be added, removed or deleted by modifying the authen_projects table of the configured database.

While the exact structure of the table will depend on the configured storage type, it is a simple schema. For PostgreSQL, the table is defined as:

create table authen_projects (
  id uuid not null primary key,
  totp_max int not null,
  totp_issuer text not null,
  totp_setup_ttl int not null,
  totp_secret_length int not null,
  created timestamptz not null default now(),
  updated timestamptz not null default now()
);

We can see from the above schema that, where it makes sense, multi-tenancy uses per-project configuration values. Specifically, when multi-tenancy is enabled, the global totp.* configuration is ignored (you'll get a warning if the totp section is defined when multi_tenancy == true).

Project Update

Internally, Authen has a non-expiring id => Project cache. While new projects will be detected, changes to existing projects will not. Setting the project_update_frequency value will cause Authen to periodically look for updated projects based on the updated column of the authen_projects. table. A value of 60 (seconds) is reasonable.

We hope to provide better way to manage projects in the future.

Data Migrations

When you run a new version of Authen, it will automatically apply any necessary data/schema migrations to the configured store.

When multiple Authen services are deployed, this behavior might not be desirable. There are two options. The first is to set the migrations configuration option to false for all but one server. Ideally, the one server that will run migrations is launched first.

The other option is to run the authen binary with the special -migrate flag. This will load the same config.json (or whatever -config PATH you specify), run any necessary migrations, and exit.