@pixlcore/xyplug-sso-aws-alb
v1.0.0
Published
A xyOps SSO Plugin for authenticating users via AWS ALB OIDC.
Readme
xyOps AWS ALB SSO Plugin
xyplug-sso-aws-alb is a custom SSO command plugin for xyOps.
It is designed for AWS Application Load Balancers that use native OIDC authentication at the listener layer. The plugin reads the ALB headers that AWS forwards to your backend, validates the ALB-signed JWT, extracts user claims, and emits standard trusted headers back to xyOps.
Please read the xyOps SSO documentation before proceeding.
What it does
The plugin reads these incoming AWS ALB headers from the xyOps SSO command request:
x-amzn-oidc-identityx-amzn-oidc-data
It then:
- Verifies the JWT from
x-amzn-oidc-datausingaws-jwt-verify. - Validates the ALB signer ARN, issuer, and client ID.
- Extracts identity and claims from the verified payload.
- Emits headers that match your xyOps
header_map, such as:x-forwarded-userx-forwarded-namex-forwarded-emailx-forwarded-groups
xyOps then continues through its normal SSO flow as if those trusted headers had been forwarded by a traditional auth proxy.
Requirements
- Node.js 18 or higher
npxavailable on the xyOps conductor- xyOps with SSO custom command support (v1.0.40+)
- An AWS ALB listener already configured for OIDC authentication
- Network egress from the xyOps conductor to the AWS ALB public key endpoint on the first verification of each new token or key ID
Install and run
The plugin is designed to be invoked by xyOps itself through sso.json.
Recommended command:
"command": "npx -y @pixlcore/[email protected]"If you prefer, you can also preinstall the package and execute it directly:
"command": "xyplug-sso-aws-alb"That works if the executable is already on your server PATH.
How xyOps calls the plugin
xyOps launches the command before its normal SSO header processing. It sends a one-line JSON document to STDIN containing:
- the full
SSOconfig object - the request headers
- parsed cookies
- request IP metadata
The plugin must return a one-line JSON document on STDOUT using the xyOps wire protocol:
{
"xy": 1,
"code": 0,
"headers": {
"x-forwarded-user": "[email protected]",
"x-forwarded-name": "Example User",
"x-forwarded-email": "[email protected]",
"x-forwarded-groups": "admins,operators"
}
}Important:
- Logical auth failures are returned with
code: 1in JSON. - The plugin is intentionally written to emit JSON and exit normally, so xyOps can display a meaningful SSO error.
Recommended xyOps configuration
Edit /opt/xyops/conf/sso.json and configure SSO like this:
{
"enabled": true,
"whitelist": ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"],
"header_map": {
"username": "x-forwarded-user",
"full_name": "x-forwarded-name",
"email": "x-forwarded-email",
"groups": "x-forwarded-groups"
},
"cleanup_username": false,
"cleanup_full_name": false,
"group_role_map": {},
"group_privilege_map": {},
"command": "npx -y @pixlcore/[email protected]",
"jwt": {
"provider": "aws_alb",
"header": "x-amzn-oidc-data",
"claim_map": {
"username": "email",
"email": "email",
"full_name": "name",
"groups": "groups"
},
"aws_alb": {
"alb_arn": "arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/example/abc123",
"issuer": "https://accounts.google.com",
"client_id": "your-oidc-client-id",
"grace_seconds": 0
}
}
}This works because:
- xyOps only cares about the standard trusted headers in
header_map - xyOps ignores the custom
jwtblock - the plugin reads
jwtfrom the command input, verifies the AWS token, and prints matching headers back out
Header map recommendations
Recommended header map:
"header_map": {
"username": "x-forwarded-user",
"full_name": "x-forwarded-name",
"email": "x-forwarded-email",
"groups": "x-forwarded-groups"
}Using distinct headers is the cleanest configuration.
You can map multiple xyOps fields to the same header name if you want, for example:
"header_map": {
"username": "x-forwarded-user",
"full_name": "x-forwarded-user",
"email": "x-forwarded-email",
"groups": "x-forwarded-groups"
}In that case, the plugin emits only one value for the shared header name. This is useful when you want xyOps to derive a prettier full name using cleanup_full_name.
Recommendation:
- Use distinct headers if you want the plugin to pass a real full name.
- Reuse the username header only if you intentionally want xyOps to derive the display name itself.
AWS JWT configuration
The plugin expects a jwt object in sso.json.
jwt.provider
Set this to:
"provider": "aws_alb"jwt.header
This is the incoming request header that contains the ALB JWT.
For AWS ALB, use:
"header": "x-amzn-oidc-data"jwt.claim_map
This tells the plugin which verified claims to use for xyOps fields.
Example:
"claim_map": {
"email": "email",
"full_name": "name",
"groups": "groups"
}Each value may be:
- a string claim name such as
"email" - a dotted path such as
"custom.profile.name" - an array of fallbacks such as
["name", "preferred_username", "email"]
Common mappings:
username: oftenemailorpreferred_usernameemail: usuallyemailfull_name: usuallynamegroups: depends on your IdP, oftengroups,roles, or a custom claim
jwt.aws_alb.alb_arn
This must match the AWS ALB signer ARN.
Example:
"alb_arn": "arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/example/abc123"You may also provide an array if you trust multiple ALBs for the same issuer:
"alb_arn": [
"arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/alb-a/abc123",
"arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/alb-b/def456"
]jwt.aws_alb.issuer
This must match the JWT iss claim from your OIDC provider.
Examples:
https://accounts.google.comhttps://YOUR_OKTA_DOMAIN/oauth2/defaulthttps://cognito-idp.us-west-2.amazonaws.com/us-west-2_ABC123
jwt.aws_alb.client_id
This must match the ALB JWT client identifier.
Example:
"client_id": "your-oidc-client-id"You may also set it to null if you intentionally want to skip client validation, but that is not recommended.
jwt.aws_alb.grace_seconds
Optional clock-skew allowance in seconds.
Example:
"grace_seconds": 30Optional advanced settings
The plugin also understands these optional properties:
"jwt": {
"cache_dir": "/tmp/xyplug-sso-aws-alb-cache",
"aws_alb": {
"jwks_uri": "https://public-keys.auth.elb.us-west-2.amazonaws.com"
}
}Notes:
cache_diroverrides the default on-disk token cache path.jwks_uriis mainly useful for advanced testing. In normal AWS setups you should omit it and letaws-jwt-verifyderive the correct ALB public key endpoint from the ALB ARN region.
Username, email, and full name behavior
The plugin resolves values in this general order:
Username
Preferred sources:
claim_map.username- common fallback claims such as
preferred_username,username,email x-amzn-oidc-identity- JWT
sub
Note:
- AWS ALB sets
x-amzn-oidc-identityto the OIDCsubclaim. - In many environments that is a stable opaque identifier rather than a friendly username.
- For that reason, the plugin prefers mapped claims first and uses the ALB identity header as a fallback.
Preferred sources:
claim_map.email- common
emailclaim - username fallback
This fallback exists because current xyOps SSO requires both username and email to be present.
Recommendation:
- Make sure your IdP returns a real email claim if you want a proper user email in xyOps.
Full name
Preferred sources:
claim_map.full_name- common
nameclaim given_name + family_name- username
If your IdP does not provide a full name, that is fine. You can:
- map
full_nameto its own header and let the plugin fall back automatically - or map
full_nameto the same header asusernameoremailand let xyOps derive a prettier display name viacleanup_full_name
Groups, roles, and privileges
The plugin returns groups as a single trusted header, usually x-forwarded-groups.
xyOps then consumes that header through its normal SSO settings:
group_role_mapgroup_privilege_mapgroup_role_separatorreplace_rolesreplace_privileges
Example:
"header_map": {
"groups": "x-forwarded-groups"
},
"group_role_map": {
"devops": ["r12345"],
"oncall": ["r67890"]
},
"group_privilege_map": {
"platform-admins": ["admin"],
"ticket-managers": ["edit_tickets", "delete_tickets"]
}If the verified JWT contains:
{
"groups": ["devops", "platform-admins"]
}The plugin will emit:
"x-forwarded-groups": "devops,platform-admins"Then xyOps will:
- add role
r12345 - grant the
adminprivilege
Group separator
If your group claim is an array, the plugin joins it using your xyOps group_role_separator.
Example:
"group_role_separator": "|"In that case the plugin will emit:
"x-forwarded-groups": "devops|platform-admins"If your JWT claim is already a string, the plugin passes it through as-is. So if your IdP returns a string, make sure its delimiter matches the separator you configured in xyOps.
Performance and caching
To keep things fast, the plugin maintains a small on-disk cache keyed by:
- the raw ALB JWT
- the relevant SSO config
Default cache path:
/tmp/xyplug-sso-aws-alb-cacheThe cache stores only:
- the verified token expiration time
- the generated trusted headers
The cache expires automatically when the JWT expires.
Security notes
- The plugin validates the ALB JWT signature with
aws-jwt-verify. - The plugin validates the ALB signer ARN.
- The plugin validates the issuer.
- The plugin validates the client ID unless you explicitly disable that by setting
client_idtonull. - If both
x-amzn-oidc-identityand JWTsubare present, the plugin checks that they match. - The plugin does not perform IP/CIDR validation. xyOps still does that via
SSO.whitelist.
Infrastructure recommendations:
- Restrict your xyOps target so only the ALB can reach it.
- Keep
SSO.whitelistenabled. - Use HTTPS end-to-end where possible.
What this plugin does not do
- It does not replace xyOps SSO user creation, session creation, or group mapping.
- It does not manage logout behavior for AWS ALB auth cookies.
- It does not validate source IPs. xyOps still does that.
- It does not mutate any xyOps config on disk.
Troubleshooting
Login fails with a JWT verification error
Check:
jwt.aws_alb.alb_arnjwt.aws_alb.issuerjwt.aws_alb.client_id- network access to the AWS ALB public key endpoint
Groups are missing
Check:
- that your IdP is actually returning the group claim
- the
jwt.claim_map.groupsvalue - your xyOps
group_role_separator
Usernames look strange
Remember that xyOps has its own username normalization rules.
If your OIDC sub contains characters outside the xyOps username character set, xyOps may normalize them. If you prefer friendlier usernames, map username to another claim that is stable in your environment.
Need more debugging output
On the xyOps side:
- set
debug_levelto9 - inspect
logs/SSO.log
On the plugin side:
- set
XYP_SSO_DEBUG=1in the environment for the xyOps service to have the plugin write extra diagnostic information to STDERR
xyOps logs raw plugin STDOUT and STDERR at SSO debug level 9.
Local development
To syntax-check the CLI:
npm testTo run it manually with a saved JSON input:
cat sample-input.json | node index.jsThe plugin expects the same XYWP payload that xyOps sends to custom SSO commands.
