{"id":26829960,"url":"https://github.com/yaegashi/azure-easy-auth-njs","last_synced_at":"2026-05-05T12:33:21.330Z","repository":{"id":284006475,"uuid":"953320162","full_name":"yaegashi/azure-easy-auth-njs","owner":"yaegashi","description":"Azure Easy Auth NJS Module","archived":false,"fork":false,"pushed_at":"2025-03-23T15:54:00.000Z","size":4,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-23T16:37:46.100Z","etag":null,"topics":["azure","azure-app-service","azure-container-apps","nginx","njs"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yaegashi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-03-23T04:30:52.000Z","updated_at":"2025-03-23T16:01:38.000Z","dependencies_parsed_at":"2025-03-23T16:37:50.670Z","dependency_job_id":"93e107e9-81e1-4865-9537-14892e1cbaf2","html_url":"https://github.com/yaegashi/azure-easy-auth-njs","commit_stats":null,"previous_names":["yaegashi/azure-easy-auth-njs"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/yaegashi/azure-easy-auth-njs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yaegashi%2Fazure-easy-auth-njs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yaegashi%2Fazure-easy-auth-njs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yaegashi%2Fazure-easy-auth-njs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yaegashi%2Fazure-easy-auth-njs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yaegashi","download_url":"https://codeload.github.com/yaegashi/azure-easy-auth-njs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yaegashi%2Fazure-easy-auth-njs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32649573,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-05T11:29:49.557Z","status":"ssl_error","status_checked_at":"2026-05-05T11:29:48.587Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["azure","azure-app-service","azure-container-apps","nginx","njs"],"created_at":"2025-03-30T13:29:21.819Z","updated_at":"2026-05-05T12:33:21.311Z","avatar_url":"https://github.com/yaegashi.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Azure Easy Auth NJS Module\n\n## Introduction\n\nThis project provides an [NJS (NGINX JavaScript)](https://nginx.org/en/docs/njs/) module for decoding and utilizing Azure Easy Auth headers in NGINX configurations.\n\nIt enables NGINX to interpret and leverage authentication information provided by\n[Azure App Service](https://learn.microsoft.com/en-us/azure/app-service/overview-authentication-authorization) or\n[Azure Container Apps](https://learn.microsoft.com/en-us/azure/container-apps/authentication) built-in authentication.\n\n## Module Capabilities\n\nThe [azure_easy_auth.js](njs/azure_easy_auth.js) module provides functionality to handle authentication data from Azure Easy Auth in NGINX environments. Key capabilities include:\n\n- **HTTP Header Access**: Provides direct access to Azure Easy Auth HTTP headers\n  - `getHeaderClientPrincipal()`: Retrieves raw `X-MS-CLIENT-PRINCIPAL` header value\n  - `getHeaderClientPrincipalId()`: Retrieves `X-MS-CLIENT-PRINCIPAL-ID` header directly\n  - `getHeaderClientPrincipalName()`: Retrieves `X-MS-CLIENT-PRINCIPAL-NAME` header directly\n  - `getHeaderClientPrincipalIdp()`: Retrieves `X-MS-CLIENT-PRINCIPAL-IDP` header directly\n  - `decodeHeaderClientPrincipal()`: Decodes base64 header into JSON object\n- **Identity Functions**: Provides convenient methods to access common user identity attributes encoded in `X-MS-CLIENT-PRINCIPAL`:\n  - `getClaimEmail()`: Retrieves user's email address\n  - `getClaimName()`: Extracts display name\n  - `getClaimObjectId()`: Obtains unique object identifier \n  - `getClaimPreferredUsername()`: Gets preferred username\n  - `getClaimGroups()`: Lists all group memberships\n  - `getClaimValue()`, `getClaimValues()`: Accesses custom claim fields\n- **Authorization Utilities**: Simplifies access control implementation:\n  - `hasClaimGroup()`: Verifies if user belongs to a specific group\n  - `isAuthorizedPrincipals()`: Implements RBAC by checking user's object ID and group memberships against allowed principals\n\nAzure Easy Auth exposes authentication context through HTTP headers as documented in [Microsoft Learn](https://learn.microsoft.com/en-us/azure/app-service/configure-authentication-user-identities):\n\n- `X-MS-CLIENT-PRINCIPAL`: Base64-encoded JSON containing all claims\n- `X-MS-CLIENT-PRINCIPAL-ID`: User's unique identifier\n- `X-MS-CLIENT-PRINCIPAL-NAME`: User's name or username\n- `X-MS-CLIENT-PRINCIPAL-IDP`: Identity provider identifier (e.g., \"aad\" for Microsoft Entra ID)\n\n## Docker Images\n\n### Overview\n\nThis project publishes customized Docker images based on [the official NGINX images](https://hub.docker.com/_/nginx):\n\n```\nghcr.io/yaegashi/azure-easy-auth-njs/nginx:VERSION\n```\n\nHere, `VERSION` corresponds to the base version, such as `1.27.4` or `latest`.\n\nThese images are designed to support the following projects:\n\n- [yaegashi/azdops-nginx-aas](https://github.com/yaegashi/azdops-nginx-aas) (for Azure App Service)\n- [yaegashi/azdops-nginx-aca](https://github.com/yaegashi/azdops-nginx-aca) (for Azure Container Apps)\n\n### Features\n\nThe Docker image includes the following features:\n\n- The content of the [njs](njs) folder is available in `/etc/nginx/njs`.\n- The following directives are added to `/etc/nginx/nginx.conf`:\n    ```\n    load_module modules/ngx_http_js_module.so;\n    js_path /etc/nginx/njs;\n    js_path /nginx/njs;\n    ```\n- You can mount a persistent volume, such as an Azure Files share, at `/nginx` in the container.\nWhen `/nginx` exists, the following persistent folders will be created:\n    | Persistent Folder       | Description                                                                 |\n    |-------------------------|-----------------------------------------------------------------------------|\n    | `/nginx/templates`      | Configuration template folder. Symlinked to `/etc/nginx/templates`. The default `default.conf.template` will be placed if empty. |\n    | `/nginx/sites/default`  | Default document root folder. The default `index.html` will be placed if empty. |\n    | `/nginx/njs`            | NJS module folder. Included in the NJS search paths via `js_path`.          |\n    | `/nginx/logs`           | Log output folder.                                                         |\n\n    Additionally, it monitors `/etc/nginx/templates` and reloads the NGINX server when content changes are detected.\n- It launches an OpenSSH server (sshd) when the container runs on Azure App Service.\n\nYou can customize NGINX using `/nginx/templates/default.conf.template` in the persistent folder.\n\n`${NGINX_HOST}` and `${NGINX_PORT}` in template files will be replaced with the corresponding environment variables using `envsubst`.\n\n### Logging\n\nFluent Bit aggregates logs from NGINX (access/error) and other services (monitor) into a single container stdout stream. The output follows this format, with a timestamp, log origin, and the actual log message:\n\n```\n1743312054.855268 monitor 2025-03-30T05:20:54.85518 I: Change detected in /etc/nginx/templates\n1743312054.860397 monitor 2025-03-30T05:20:54.86034 20-envsubst-on-templates.sh: Running envsubst on /etc/nginx/templates/default.conf.template to /etc/nginx/conf.d/default.conf\n1743312054.869931 monitor 2025-03-30T05:20:54.86986 nginx: the configuration file /etc/nginx/nginx.conf syntax is ok\n1743312054.870034 monitor 2025-03-30T05:20:54.86997 nginx: configuration file /etc/nginx/nginx.conf test is successful\n1743312054.870827 monitor 2025-03-30T05:20:54.87075 I: Reloading nginx service\n1743312054.872446 monitor 2025-03-30T05:20:54.87156 ok: run: /docker/service/nginx: (pid 54) 3751s\n1743312054.975864 error 2025/03/30 05:20:54 [notice] 2376#2376: gracefully shutting down\n1743312054.975869 error 2025/03/30 05:20:54 [notice] 2375#2375: gracefully shutting down\n1743312054.975870 error 2025/03/30 05:20:54 [notice] 2376#2376: exiting\n1743312054.975870 error 2025/03/30 05:20:54 [notice] 2375#2375: exiting\n1743312054.975871 error 2025/03/30 05:20:54 [notice] 2375#2375: exit\n1743312054.975871 error 2025/03/30 05:20:54 [notice] 2376#2376: exit\n1743312054.985042 error 2025/03/30 05:20:54 [notice] 54#54: signal 17 (SIGCHLD) received from 2375\n1743312054.985065 error 2025/03/30 05:20:54 [notice] 54#54: worker process 2375 exited with code 0\n1743312054.985148 error 2025/03/30 05:20:54 [notice] 54#54: worker process 2376 exited with code 0\n1743312054.985169 error 2025/03/30 05:20:54 [notice] 54#54: signal 29 (SIGIO) received\n1743312082.555455 access 172.18.0.1 - - [30/Mar/2025:05:21:22 +0000] \"GET / HTTP/1.1\" 304 0 \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36\" \"10.240.2.23\"\n1743312088.008587 access 172.18.0.1 - - [30/Mar/2025:05:21:28 +0000] \"GET /abc HTTP/1.1\" 404 555 \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36\" \"10.240.2.23\"\n1743312088.008511 error 2025/03/30 05:21:28 [error] 2408#2408: *6 open() \"/nginx/sites/default/abc\" failed (2: No such file or directory), client: 172.18.0.1, server: _, request: \"GET /abc HTTP/1.1\", host: \"localhost:8080\"\n```\n\nOn container startup, it creates a log file in the persistent folder following this naming convention:\n\n```\n/nginx/logs/%Y/%m/%d/%Y%m%dT%H%M%S_{container_hostname}.log\n```\n\nThe log file contains the same output that is streamed to stdout, providing both real-time monitoring and persistent logging capabilities.\n\n### Default Template Configuration\n\nOn the container starting,\n[docker/default.conf.template](docker/default.conf.template) are copied to `/nginx/templates/default.conf.template` if not exists.\n\n```\nserver {\n    listen ${NGINX_PORT} default_server;\n    server_name _;\n\n    root /nginx/sites/default;\n}\n```\n\n### Secure Path Configuration\n\nPut the following in `/nginx/template/default.conf.template`:\n\n```\nserver {\n    listen ${NGINX_PORT};\n    server_name _;\n\n    root /nginx/sites/default;\n\n    js_import auth from azure_easy_auth.js;\n    js_set $is_authorized auth.isAuthorizedPrincipals;\n\n    location ~ ^/secure/ {\n        set $authorized_principals \"98a7b6c5-d4e3-21f0-9g8h-765432109876\";\n        if ($is_authorized = \"0\") {\n            return 403 \"Forbidden\";\n        }\n    }\n}\n```\n\nThis configuration secures paths beginning with `/secure/` using the `isAuthorizedPrincipals` function. The function takes the `$authorized_principals` NGINX variable as input, which can contain multiple comma-separated GUIDs. The function returns `\"1\"` if any of these GUIDs match the authenticated user's object ID or group claims, or `\"0\"` if no match is found. Access is denied with a 403 response when the function returns `\"0\"`.\n\n### Dynamic Secure Path Configuration\n\n```\nserver {\n    listen ${NGINX_PORT};\n    server_name _;\n\n    root /nginx/sites/default;\n\n    js_import auth from azure_easy_auth.js;\n    js_set $is_authorized auth.isAuthorizedPrincipals;\n    set $super_principal \"123e4567-e89b-12d3-a456-426614174000\";\n\n    location ~ ^/secure/(?\u003csecured_principal\u003e[^/]+) {\n        set $authorized_principals \"$secured_principal,$super_principal\";\n        if ($is_authorized = \"0\") {\n            return 403 \"Forbidden\";\n        }\n    }\n}\n```\n\nThis configuration demonstrates dynamic permission setting based on the request path. It extracts a `$secured_principal` (GUID) from paths like `/secure/{GUID}/...` and combines it with an always-authorized `$super_principal` to create the `$authorized_principals` variable.\nAccess is granted only if the user's object ID or group claims match either the `$secured_principal` or the `$super_principal`.\n\n### Multiple Subdomain Configuration\n\n```\nmap $host $site_name {\n    default default;\n    ~*^${NGINX_HOST}$ default;\n    ~*^(.+)\\.${NGINX_HOST}$ $1;\n}\n\nserver {\n    listen ${NGINX_PORT};\n    server_name .${NGINX_HOST};\n\n    root /nginx/sites/$site_name;\n\n    js_import auth from azure_easy_auth.js;\n    js_set $is_authorized auth.isAuthorizedPrincipals;\n\n    if ($site_name ~* ^pr-review-) {\n        # Set PR reviewer's group principal\n        set $authorized_principals \"85b93f9c-7d2e-4a80-b71c-425ae32f1cc1\";\n        if ($is_authorized = \"0\") {\n            return 403 \"Forbidden\";\n        }\n    }\n}\n\nserver {\n    listen ${NGINX_PORT} default_server;\n    server_name _;\n    return 404 \"Not Found\";\n}\n```\n\nThis configuration serves different content based on subdomains. When `NGINX_HOST=example.com`, it behaves as follows:\n\n- Access to the main domain (`example.com`) serves content from `/nginx/sites/default`\n- Regular subdomains (e.g., `blog.example.com`) serve content from their corresponding subdirectories (`/nginx/sites/blog`)\n- Pull request review subdomains (e.g., `pr-review-123.example.com`) are protected and only accessible by authenticated users in the group `85b93f9c-7d2e-4a80-b71c-425ae32f1cc1`\n- Access to any undefined domain (e.g., `example.net`) returns a 404 error\n\nThis pattern is particularly useful in team development environments where you might create dedicated preview environments for each pull request, accessible only to specific reviewers.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyaegashi%2Fazure-easy-auth-njs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyaegashi%2Fazure-easy-auth-njs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyaegashi%2Fazure-easy-auth-njs/lists"}