Run the service as a containerized application (the below examples use docker-compose syntax).
Set the SERVICE_AUTHENTICATION_REST_PORT env variable to whatever port you want to use for the service.
services:
postgres-authentication:
image: ghcr.io/a-novel/service-authentication/database:v2.4.2
networks:
- api
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
POSTGRES_DB: postgres
POSTGRES_HOST_AUTH_METHOD: scram-sha-256
POSTGRES_INITDB_ARGS: --auth=scram-sha-256
volumes:
- authentication-postgres-data:/var/lib/postgresql/
service-authentication:
image: ghcr.io/a-novel/service-authentication/standalone:v2.4.2
ports:
- "${SERVICE_AUTHENTICATION_REST_PORT}:8080"
depends_on:
postgres-authentication:
condition: service_healthy
environment:
POSTGRES_DSN: "postgres://postgres:postgres@postgres-authentication:5432/postgres?sslmode=disable"
SERVICE_JSON_KEYS_PORT: # Port where service-json-keys is running
SERVICE_JSON_KEYS_HOST: # URL to a running service-json-keys instance
networks:
- api
networks:
api:
volumes:
authentication-postgres-data:Note the standalone image is an all-in-one initializer for the application; however, it runs heavy operations such as migrations on every launch. Thus, while it comes in handy for local development, it is NOT RECOMMENDED for production deployments. Instead, consider using the separate, optimized images for that purpose.
services:
postgres-authentication:
image: ghcr.io/a-novel/service-authentication/database:v2.4.2
networks:
- api
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
POSTGRES_DB: postgres
POSTGRES_HOST_AUTH_METHOD: scram-sha-256
POSTGRES_INITDB_ARGS: --auth=scram-sha-256
volumes:
- authentication-postgres-data:/var/lib/postgresql/
migrations-authentication:
image: ghcr.io/a-novel/service-authentication/migrations:v2.4.2
depends_on:
postgres-authentication:
condition: service_healthy
environment:
POSTGRES_DSN: "postgres://postgres:postgres@postgres-authentication:5432/postgres?sslmode=disable"
networks:
- api
# Optional job, used to inject base data into a freshly initialized database.
init-authentication:
image: ghcr.io/a-novel/service-authentication/init:v2.4.2
depends_on:
postgres-authentication:
condition: service_healthy
migrations-authentication:
condition: service_completed_successfully
environment:
POSTGRES_DSN: "postgres://postgres:postgres@postgres-authentication:5432/postgres?sslmode=disable"
# Create an initial super admin user. Make sure those credentials are passed in a secure manner.
SUPER_ADMIN_EMAIL: # Email for the initial super admin user
SUPER_ADMIN_PASSWORD: # Unencrypted password for the initial super admin user
networks:
- api
service-authentication:
image: ghcr.io/a-novel/service-authentication/rest:v2.4.2
ports:
- "${SERVICE_AUTHENTICATION_REST_PORT}:8080"
depends_on:
postgres-authentication:
condition: service_healthy
migrations-authentication:
condition: service_completed_successfully
init-authentication:
condition: service_completed_successfully
environment:
POSTGRES_DSN: "postgres://postgres:postgres@postgres-authentication:5432/postgres?sslmode=disable"
SERVICE_JSON_KEYS_PORT: # Port where service-json-keys is running
SERVICE_JSON_KEYS_HOST: # URL to a running service-json-keys instance
networks:
- api
networks:
api:
volumes:
authentication-postgres-data:Above are the minimal required configuration to run the service locally. Configuration is done through environment variables. Below is a list of available configurations:
Required variables
| Name | Description | Images |
|---|---|---|
| POSTGRES_DSN | The Postgres Data Source Name (DSN) used to connect to the database. | standalonerestinitmigrations |
| SERVICE_JSON_KEYS_PORT | Port where service-json-keys is running | standalonerest |
| SERVICE_JSON_KEYS_HOST | URL to a running service-json-keys instance | standalonerest |
This service requires a running instance of the JSON Keys service. Note that the authentication and json keys service share sensitive data, they should communicate over a secure, unexposed network.
Platform connection
You can provide a connection to the client platform by pointing to a running instance of the authentication platform.
This connection is optional and only used to populate links in emails.
| Name | Description | Default value | Images |
|---|---|---|---|
| PLATFORM_AUTH_URL | URL to the client platform (optional if all other values are provided) | standalonerest |
|
| PLATFORM_AUTH_URL_UPDATE_EMAIL | URL to the client platform email validation page | PLATFORM_AUTH_URL + /ext/email/validate |
standalonerest |
| PLATFORM_AUTH_URL_UPDATE_PASSWORD | URL to the client platform password update page | PLATFORM_AUTH_URL + /ext/password/reset |
standalonerest |
| PLATFORM_AUTH_URL_REGISTER | URL to the client platform register page | PLATFORM_AUTH_URL + /ext/account/create |
standalonerest |
SMTP
By default, this service uses a mock email sender that prints email content to the standard output. It is highly recommended to set an actual SMTP server in production environments, as emails contain sensitive information (eg short codes).
| Name | Description | Default value | Images |
|---|---|---|---|
| SMTP_ADDR | Address of the SMTP server (domain:port) |
standalonerest |
|
| SMTP_SENDER_NAME | Name that will appear as the sender in outgoing emails | standalonerest |
|
| SMTP_SENDER_EMAIL | Email address used to send outgoing smtp emails | standalonerest |
|
| SMTP_SENDER_PASSWORD | Plain password used to connect to the sender email account, this data is sensitive so handle with care | standalonerest |
|
| SMTP_SENDER_DOMAIN | Domain used for sending Smtp emails | standalonerest |
|
| SMTP_TIMEOUT | Set the timeout for sending emails | 20s |
standalonerest |
| SMTP_FORCE_UNENCRYPTED | DO NOT SET IN PRODUCTION. This variable bypasses SMTP security by allowing plain credentials over insecure connections. This setting is intended for development only, and could compromise your credentials in production. | false |
standalonerest |
Rest API
While you should not need to change these values in most cases, the following variables allow you to customize the API behavior.
| Name | Description | Default value | Images |
|---|---|---|---|
| REST_MAX_REQUEST_SIZE | Maximum size of incoming requests in bytes | 2097152 (2MiB) |
standalonerest |
| REST_TIMEOUT_READ | Timeout for read operations | 15s |
standalonerest |
| REST_TIMEOUT_READ_HEADER | Timeout for header reading operations | 3s |
standalonerest |
| REST_TIMEOUT_WRITE | Timeout for write operations | 30s |
standalonerest |
| REST_TIMEOUT_IDLE | Idle timeout | 60s |
standalonerest |
| REST_TIMEOUT_REQUEST | Timeout for api requests | 60s |
standalonerest |
| REST_CORS_ALLOWED_ORIGINS | CORS allowed origins (allow all by default) | * |
standalonerest |
| REST_CORS_ALLOWED_HEADERS | CORS allowed headers (allow all by default) | * |
standalonerest |
| REST_CORS_ALLOW_CREDENTIALS | CORS allow credentials | false |
standalonerest |
| REST_CORS_MAX_AGE | CORS max age | 3600 |
standalonerest |
Logs & Tracing
For now, OTEL is only provided using 2 exporters: stdout and Google Cloud. Other integrations may come in the future.
| Name | Description | Default value | Images |
|---|---|---|---|
| OTEL | Activate OTEL tracing (use options below to switch between exporters) | false |
standalonerestinit |
| GCLOUD_PROJECT_ID | Google Cloud project id for the OTEL exporter. Switch to Google Cloud exporter when set | standalonerestinit |
|
| APP_NAME | Application name to be used in traces | service-authentication |
standalonerestinit |
Setup
The below variables allow you to setup an empty instance through a job, injecting the minimum data to use the application.
| Name | Description | Images |
|---|---|---|
| SUPER_ADMIN_EMAIL | Email for the initial super admin user | standaloneinit |
| SUPER_ADMIN_PASSWORD | Unencrypted password for the initial super admin user | standaloneinit |
To interact with a running instance of the authentication service, you can use the integrated package.
⚠️ Warning: Even though the package is public, GitHub registry requires you to have a Personal Access Token withrepoandread:packagesscopes to pull it in your project. See this issue for more information.
Make sure you have a .npmrc with the following content (in your project or in your home directory):
@a-novel:registry=https://npm.pkg.github.com
@a-novel-kit:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${YOUR_PERSONAL_ACCESS_TOKEN}Then, install the package using pnpm:
# pnpm config set auto-install-peers true
# Or
# pnpm config set auto-install-peers true --location project
pnpm add @a-novel/service-authentication-restTo use it, you must create an AuthenticationApi instance. A single instance can be shared across
your client.
import { AuthenticationApi, tokenCreateAnon } from "@a-novel/service-authentication-rest";
export const authenticationApi = new AuthenticationApi("<base_api_url>");
// (optional) check the status of the api connection.
await authenticationApi.ping();
await authenticationApi.health();You can then call methods from the package using this api instance. Each method comes with zod types so you can validate requests easily.
Responses are validated by default.
import {
ClaimsSchema,
CredentialsCreateRequestSchema,
CredentialsExistsRequestSchema,
CredentialsGetRequestSchema,
CredentialsListRequestSchema,
CredentialsResetPasswordRequestSchema,
CredentialsSchema,
CredentialsUpdateEmailRequestSchema,
CredentialsUpdatePasswordRequestSchema,
CredentialsUpdateRoleRequestSchema,
ShortCodeCreateEmailUpdateRequestSchema,
ShortCodeCreatePasswordResetRequestSchema,
ShortCodeCreateRegisterRequestSchema,
TokenCreateRequestSchema,
TokenRefreshRequestSchema,
TokenSchema,
claimsGet,
credentialsCreate,
credentialsExists,
credentialsGet,
credentialsList,
credentialsResetPassword,
credentialsUpdateEmail,
credentialsUpdatePassword,
credentialsUpdateRole,
shortCodeCreateEmailUpdate,
shortCodeCreatePasswordReset,
shortCodeCreateRegister,
tokenCreate,
tokenCreateAnon,
tokenRefresh,
} from "@a-novel/service-authentication-rest";You can integrate the authentication capabilities directly into your Go services by using the provided Go module. Note this does not require a running instance of the authentication service, but only of its JSON Keys service dependency.
go get -u github.com/a-novel/service-authentication/v2package main
import (
"context"
"os"
loggingpresets "github.com/a-novel-kit/golib/logging/presets"
"github.com/go-chi/chi/v5"
"github.com/muesli/termenv"
"github.com/a-novel/service-authentication/v2/pkg/go"
"github.com/a-novel/service-json-keys/v2/pkg/go"
)
// Define roles for your application (required).
//
// The key is the name of the role, as it is passed to the JWT payload.
// Permissions are evaluated at path level, which means a given role can
// only access resources for which it has explicit permissions. For more
// fine-grained access, you must implement custom validation yourself.
//
// The priority argument serves as a hierarchy indicator between roles, and is
// used by some custom access checks to grant permission for an operation between
// 2 users, based on their relative roles priorities.
var myPermissions = serviceauthentication.Permissions{
Roles: map[string]serviceauthentication.Role{
"role1": {
Priority: 0,
Permissions: []string{"permission1", "permission2"},
},
"role2": {
Priority: 1,
Inherits: []string{"role1"},
Permissions: []string{"permission3"},
},
"role3": {
Priority: 0,
Permissions: []string{"permission4"},
},
},
}
func main() {
ctx := context.Background()
jsonKeysClient, _ := servicejsonkeys.NewClient("<service-json-keys-url>")
serviceVerifyAccessToken := servicejsonkeys.NewClaimsVerifier[serviceauthentication.Claims](jsonKeysClient)
logger := &loggingpresets.LogLocal{
Out: os.Stdout,
}
// You can now add permission-based authentication to your routes.
withAuth := serviceauthentication.NewAuthHandler(serviceVerifyAccessToken, myPermissions, logger)
router := chi.NewRouter()
// Route only accessible to users with role2.
withAuth(router, "permission3").Get(...)
// Route accessible to users with role1 or role2.
withAuth(router, "permission2").Get(...)
// Route accessible to all authenticated users.
withAuth(router).Get(...)
// Route accessible to users with role2 or role3.
withAuth(router, "permission3", "permission4").Get(...)
}