Skip to content

Authentication

NORA supports htpasswd-based authentication with API tokens for programmatic access. Authentication is disabled by default and must be explicitly enabled.


Set the NORA_AUTH_ENABLED environment variable or configure it in config.toml:

Terminal window
# Environment variable
export NORA_AUTH_ENABLED=true
config.toml
[auth]
enabled = true
htpasswd_file = "users.htpasswd"
token_storage = "data/tokens"

NORA uses Apache-compatible htpasswd files for user management. Create a password file using htpasswd (from apache2-utils) or any compatible tool:

Terminal window
# Install htpasswd (Debian/Ubuntu)
apt-get install apache2-utils
# Create file with first user
htpasswd -Bc users.htpasswd admin
# Add additional users
htpasswd -B users.htpasswd developer
htpasswd -B users.htpasswd ci-bot

The -B flag uses bcrypt hashing, which is the recommended algorithm.

Docker:

Terminal window
docker run -d \
--name nora \
-p 4000:4000 \
-v /data/nora:/data \
-v /etc/nora/users.htpasswd:/app/users.htpasswd:ro \
-e NORA_AUTH_ENABLED=true \
-e NORA_AUTH_HTPASSWD_FILE=/app/users.htpasswd \
ghcr.io/getnora-io/nora:latest

Kubernetes:

apiVersion: v1
kind: Secret
metadata:
name: nora-htpasswd
type: Opaque
stringData:
users.htpasswd: |
admin:$2y$05$...
ci-bot:$2y$05$...
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nora
spec:
template:
spec:
containers:
- name: nora
env:
- name: NORA_AUTH_ENABLED
value: "true"
- name: NORA_AUTH_HTPASSWD_FILE
value: /etc/nora/users.htpasswd
volumeMounts:
- name: htpasswd
mountPath: /etc/nora
readOnly: true
volumes:
- name: htpasswd
secret:
secretName: nora-htpasswd

When NORA_AUTH_ANONYMOUS_READ=true, unauthenticated users can pull/download artifacts, but authentication is still required for push/upload operations.

Terminal window
export NORA_AUTH_ENABLED=true
export NORA_AUTH_ANONYMOUS_READ=true
config.toml
[auth]
enabled = true
anonymous_read = true

This is useful for organizations that want open read access (e.g., shared libraries) while restricting who can publish artifacts.

OperationAnonymous Read = falseAnonymous Read = true
Pull / DownloadAuth requiredNo auth needed
Push / UploadAuth requiredAuth required
Delete / AdminAuth requiredAuth required

API tokens provide programmatic access without exposing htpasswd credentials. Tokens are prefixed with nra_ for easy identification and use Argon2 hashing.

RolePermissions
readPull and download artifacts only
writePull, push, and download artifacts
adminFull access including token management
Terminal window
curl -X POST http://localhost:4000/api/tokens \
-H "Content-Type: application/json" \
-d '{
"username": "admin",
"password": "your-password",
"role": "write",
"ttl_days": 90,
"description": "CI/CD pipeline token"
}'

Response:

{
"token": "nra_a1b2c3d4e5f6...",
"expires_in_days": 90
}

Save the token value immediately — it is only shown once at creation time.

Terminal window
curl -X POST http://localhost:4000/api/tokens/list \
-H "Content-Type: application/json" \
-d '{
"username": "admin",
"password": "your-password"
}'

Response:

{
"tokens": [
{
"hash_prefix": "a1b2c3",
"created_at": 1714200000,
"expires_at": 1721976000,
"last_used": 1714300000,
"description": "CI/CD pipeline token",
"role": "write"
}
]
}

Use the hash_prefix from the list response:

Terminal window
curl -X POST http://localhost:4000/api/tokens/revoke \
-H "Content-Type: application/json" \
-d '{
"username": "admin",
"password": "your-password",
"hash_prefix": "a1b2c3"
}'

NORA supports standard Docker authentication. When auth is enabled, use docker login before push/pull operations:

Terminal window
# Login with htpasswd credentials
docker login localhost:4000
# Username: admin
# Password: ****
# Login with API token (use token as password, any username)
docker login localhost:4000 -u token -p nra_a1b2c3d4e5f6...

For automated workflows, use --password-stdin:

Terminal window
echo "nra_a1b2c3d4e5f6..." | docker login localhost:4000 -u token --password-stdin

name: Build and Push
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Login to NORA
run: |
echo "${{ secrets.NORA_TOKEN }}" | \
docker login registry.example.com -u token --password-stdin
- name: Build and Push
run: |
docker build -t registry.example.com/myapp:${{ github.sha }} .
docker push registry.example.com/myapp:${{ github.sha }}

For non-Docker registries (npm, PyPI, Cargo, etc.), use the token in the appropriate client configuration:

# npm
- name: Publish npm package
env:
NORA_TOKEN: ${{ secrets.NORA_TOKEN }}
run: |
echo "//registry.example.com/:_authToken=${NORA_TOKEN}" > .npmrc
npm publish --registry=https://registry.example.com
# PyPI (twine)
- name: Publish Python package
env:
NORA_TOKEN: ${{ secrets.NORA_TOKEN }}
run: |
twine upload --repository-url https://registry.example.com/pypi/ \
-u token -p "${NORA_TOKEN}" dist/*
stages:
- build
- publish
variables:
NORA_REGISTRY: registry.example.com
build:
stage: build
image: docker:latest
services:
- docker:dind
before_script:
- echo "$NORA_TOKEN" | docker login $NORA_REGISTRY -u token --password-stdin
script:
- docker build -t $NORA_REGISTRY/myapp:$CI_COMMIT_SHA .
- docker push $NORA_REGISTRY/myapp:$CI_COMMIT_SHA
publish-maven:
stage: publish
image: maven:3.9
script:
- >
mvn deploy
-DaltDeploymentRepository=nora::https://${NORA_REGISTRY}/maven2
-Dserver.username=token
-Dserver.password=${NORA_TOKEN}

Store NORA_TOKEN as a masked CI/CD variable in GitLab project settings.


  1. Use scoped tokens. Create read tokens for pull-only workloads and write tokens only for pipelines that publish.
  2. Set TTL. Always specify ttl_days when creating tokens. Rotate tokens regularly.
  3. Do not commit tokens. Use CI/CD secrets (GitHub Secrets, GitLab CI Variables) to inject tokens at runtime.
  4. Revoke on compromise. If a token is leaked, revoke it immediately using the API.
  5. Use anonymous read when possible. If your artifacts are not sensitive, enable NORA_AUTH_ANONYMOUS_READ=true to reduce token management overhead.