Authentication
NORA supports multiple authentication methods: htpasswd-based credentials, OIDC workload identity (for CI/CD systems), and API tokens. Authentication is disabled by default and must be explicitly enabled.
Enabling Authentication
Section titled “Enabling Authentication”Set the NORA_AUTH_ENABLED environment variable or configure it in config.toml:
# Environment variableexport NORA_AUTH_ENABLED=true[auth]enabled = truehtpasswd_file = "users.htpasswd"token_storage = "data/tokens"htpasswd Setup
Section titled “htpasswd Setup”NORA uses Apache-compatible htpasswd files for user management. Create a password file using htpasswd (from apache2-utils) or any compatible tool:
Creating the htpasswd file
Section titled “Creating the htpasswd file”# Install htpasswd (Debian/Ubuntu)apt-get install apache2-utils
# Create file with first userhtpasswd -Bc users.htpasswd admin
# Add additional usershtpasswd -B users.htpasswd developerhtpasswd -B users.htpasswd ci-botThe -B flag uses bcrypt hashing, which is the recommended algorithm.
Mount the file
Section titled “Mount the file”Docker:
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:latestKubernetes:
apiVersion: v1kind: Secretmetadata: name: nora-htpasswdtype: OpaquestringData: users.htpasswd: | admin:$2y$05$... ci-bot:$2y$05$...---apiVersion: apps/v1kind: Deploymentmetadata: name: noraspec: 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-htpasswdAnonymous Read Mode
Section titled “Anonymous Read Mode”When NORA_AUTH_ANONYMOUS_READ=true, unauthenticated users can pull/download artifacts, but authentication is still required for push/upload operations.
export NORA_AUTH_ENABLED=trueexport NORA_AUTH_ANONYMOUS_READ=true[auth]enabled = trueanonymous_read = trueThis is useful for organizations that want open read access (e.g., shared libraries) while restricting who can publish artifacts.
| Operation | Anonymous Read = false | Anonymous Read = true |
|---|---|---|
| Pull / Download | Auth required | No auth needed |
| Push / Upload | Auth required | Auth required |
| Delete / Admin | Auth required | Auth required |
OIDC Workload Identity
Section titled “OIDC Workload Identity”NORA supports OIDC (OpenID Connect) workload identity for CI/CD systems like GitHub Actions and GitLab CI. This allows pipelines to authenticate without storing long-lived secrets — the CI platform issues a short-lived JWT that NORA validates directly.
How It Works
Section titled “How It Works”- Your CI platform (GitHub Actions, GitLab CI) issues a short-lived OIDC token with claims identifying the workflow, repository, and branch.
- The pipeline sends this token as a
Bearertoken to NORA. - NORA validates the JWT signature against the provider’s JWKS endpoint, checks the issuer, audience, and lifetime, then maps the
subclaim to a role via configured rules.
No static secrets are stored in your CI — only the OIDC audience needs to be configured.
Configuration
Section titled “Configuration”[auth]enabled = true
[auth.oidc]enabled = trueleeway_secs = 60 # Clock skew tolerance (default: 60)jwks_cache_secs = 300 # JWKS key cache TTL (default: 300)
[[auth.oidc.providers]]name = "github-actions"issuer = "https://token.actions.githubusercontent.com"audience = "nora"algorithms = ["RS256", "ES256"]max_token_lifetime_secs = 900enabled = true# jwks_uri = "https://..." # Optional: explicit JWKS endpoint override
# Role rules: first match wins. Glob patterns on the `sub` claim.[[auth.oidc.providers.role_rules]]pattern = "repo:myorg/*:ref:refs/heads/main"role = "write"
[[auth.oidc.providers.role_rules]]pattern = "repo:myorg/*"role = "read"Environment variable override:
export NORA_AUTH_OIDC_ENABLED=trueGitHub Actions Setup
Section titled “GitHub Actions Setup”- Configure NORA with the GitHub OIDC issuer (as shown above).
- Add the
id-token: writepermission to your workflow. - Use the token directly — no secrets needed.
name: Publish to NORAon: push: branches: [main]
permissions: id-token: write contents: read
jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Get OIDC Token id: oidc uses: actions/github-script@v7 with: script: | const token = await core.getIDToken('nora'); core.setOutput('token', token);
- name: Push to NORA run: | # Docker echo "${{ steps.oidc.outputs.token }}" | \ docker login registry.example.com -u oidc --password-stdin docker push registry.example.com/myapp:${{ github.sha }}
# Or npm echo "//registry.example.com/:_authToken=${{ steps.oidc.outputs.token }}" > .npmrc npm publishGitLab CI Setup
Section titled “GitLab CI Setup”[[auth.oidc.providers]]name = "gitlab-ci"issuer = "https://gitlab.com" # or your self-hosted GitLab URLaudience = "nora"algorithms = ["RS256"]max_token_lifetime_secs = 7200 # GitLab tokens have 1hr lifetimeenabled = true
[[auth.oidc.providers.role_rules]]pattern = "project_path:mygroup/*:ref_type:branch:ref:main"role = "write"
[[auth.oidc.providers.role_rules]]pattern = "project_path:mygroup/*"role = "read"publish: image: docker:latest id_tokens: NORA_TOKEN: aud: nora script: - echo "$NORA_TOKEN" | docker login $NORA_REGISTRY -u oidc --password-stdin - docker push $NORA_REGISTRY/myapp:$CI_COMMIT_SHARole Rules
Section titled “Role Rules”Role rules use glob patterns matched against the JWT sub claim. The first matching rule wins.
| Pattern | Matches |
|---|---|
repo:myorg/*:ref:refs/heads/main | Any repo in myorg, main branch only |
repo:myorg/* | Any repo in myorg, any branch |
project_path:mygroup/*:ref_type:branch:ref:main | GitLab: any project, main branch |
* | Everything (catch-all) |
Available roles: read, write, admin.
JWKS Discovery
Section titled “JWKS Discovery”NORA resolves the JWKS (signing keys) endpoint for each provider using this order:
- Explicit
jwks_uriin provider config (highest priority) - OIDC Discovery via
{issuer}/.well-known/openid-configuration - Fallback to
{issuer}/.well-known/jwks.json
Most providers work automatically via step 2. Use jwks_uri when:
- Your provider’s JWKS endpoint doesn’t follow the standard path
- You need to point to an internal proxy or mirror
- OIDC discovery is blocked by a firewall
[[auth.oidc.providers]]name = "custom-idp"issuer = "https://auth.internal.corp"jwks_uri = "https://auth.internal.corp/keys/jwks.json" # explicit overrideaudience = "nora"algorithms = ["RS256"]Security Properties
Section titled “Security Properties”- Algorithm whitelist: Only RS256 and ES256 by default. Symmetric algorithms (HS256/HS384/HS512) are always rejected.
- Strict issuer binding: NORA never follows
jku/x5uheaders from the token. Keys are always fetched from the configured issuer’s JWKS endpoint. - Token lifetime ceiling: Tokens with
exp - iatexceedingmax_token_lifetime_secsare rejected, even if not yet expired. - Stale JWKS fallback: If JWKS refresh fails (network issue), NORA serves stale cached keys to maintain availability.
- Per-provider kill switch: Disable a provider instantly with
enabled = falsewithout removing its configuration.
Multiple Providers
Section titled “Multiple Providers”You can configure multiple OIDC providers simultaneously:
[[auth.oidc.providers]]name = "github-actions"issuer = "https://token.actions.githubusercontent.com"audience = "nora"# ...
[[auth.oidc.providers]]name = "gitlab-ci"issuer = "https://gitlab.example.com"audience = "nora"# ...NORA routes each token to the correct provider based on the iss claim.
API Tokens
Section titled “API Tokens”API tokens provide programmatic access without exposing htpasswd credentials. Tokens are prefixed with nra_ for easy identification and use Argon2 hashing.
Token Roles
Section titled “Token Roles”| Role | Permissions |
|---|---|
read | Pull and download artifacts only |
write | Pull, push, and download artifacts |
admin | Full access including token management |
Creating a Token
Section titled “Creating a Token”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.
Listing Tokens
Section titled “Listing Tokens”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" } ]}Revoking a Token
Section titled “Revoking a Token”Use the hash_prefix from the list response:
curl -X POST http://localhost:4000/api/tokens/revoke \ -H "Content-Type: application/json" \ -d '{ "username": "admin", "password": "your-password", "hash_prefix": "a1b2c3" }'Docker Login
Section titled “Docker Login”NORA supports standard Docker authentication. When auth is enabled, use docker login before push/pull operations:
# Login with htpasswd credentialsdocker 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:
echo "nra_a1b2c3d4e5f6..." | docker login localhost:4000 -u token --password-stdinCI/CD Integration
Section titled “CI/CD Integration”For CI/CD pipelines, prefer OIDC Workload Identity over static API tokens when your CI platform supports it (GitHub Actions, GitLab CI). OIDC eliminates secret management entirely.
If OIDC is not available, use API tokens as shown below.
GitHub Actions (with API Token)
Section titled “GitHub Actions (with API Token)”name: Build and Pushon: 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/*GitLab CI
Section titled “GitLab CI”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.
Token Security Best Practices
Section titled “Token Security Best Practices”- Use scoped tokens. Create
readtokens for pull-only workloads andwritetokens only for pipelines that publish. - Set TTL. Always specify
ttl_dayswhen creating tokens. Rotate tokens regularly. - Do not commit tokens. Use CI/CD secrets (GitHub Secrets, GitLab CI Variables) to inject tokens at runtime.
- Revoke on compromise. If a token is leaked, revoke it immediately using the API.
- Use anonymous read when possible. If your artifacts are not sensitive, enable
NORA_AUTH_ANONYMOUS_READ=trueto reduce token management overhead.
See Also
Section titled “See Also”- Configuration Reference — all environment variables
- Curation — package access control
- Production Deployment — TLS and proxy setup
- GitHub OIDC documentation — GitHub Actions OIDC setup
- GitLab CI OIDC — GitLab CI/CD ID tokens