[GH-ISSUE #6609] SSO SAML AuthnRequest Empty AssertionConsumerServiceURL #19201

Closed
opened 2026-04-15 18:01:17 -05:00 by GiteaMirror · 9 comments
Owner

Originally created by @ColaIan on GitHub (Dec 8, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/6609

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Set ACS URL as callbackUrl

		sso({
			defaultSSO: [
				{
					domain: 'localhost:5173', // Your domain
					providerId: 'pydrox-dev',
					samlConfig: {
						// SP Configuration
						issuer: 'http://localhost:5173/api/auth/sso/saml2/sp/metadata',
						entryPoint:
							'https://auth.pydrox.com/application/saml/development-better-auth-5173/sso/binding/redirect/',
						callbackUrl: 'http://localhost:5173/api/auth/sso/saml2/sp/acs/pydrox-dev',
						...

Attempt to sign in with that SAML SSO provider

Current vs. Expected behavior

After base64decoding and xml deflating the SAMLRequest param, AssertionConsumerServiceURL shows empty string

Image

Expected value of AssertionConsumerServiceURL should be the value of callbackURL

What version of Better Auth are you using?

1.4.5

System info

{
  "system": {
    "platform": "linux",
    "arch": "arm64",
    "version": "#17~24.04.1-Ubuntu SMP Tue Nov  4 16:40:23 UTC 2025",
    "release": "6.14.0-1017-oracle",
    "cpuCount": 4,
    "cpuModel": "Neoverse-N1",
    "totalMemory": "23.42 GB",
    "freeMemory": "10.68 GB"
  },
  "node": {
    "version": "v24.7.0",
    "env": "development"
  },
  "packageManager": {
    "name": "bun",
    "version": "1.3.4"
  },
  "frameworks": [
    {
      "name": "svelte",
      "version": "^5.45.6"
    },
    {
      "name": "@sveltejs/kit",
      "version": "^2.49.1"
    }
  ],
  "databases": [
    {
      "name": "@prisma/client",
      "version": "^7.1.0"
    }
  ],
  "betterAuth": {
    "version": "^1.4.5",
    "config": {
      "experimental": {
        "joins": true
      },
      "plugins": [
        {
          "name": "organization",
          "config": {
            "id": "organization",
            "endpoints": {},
            "schema": {
              "organization": {
                "fields": {
                  "name": {
                    "type": "string",
                    "required": true,
                    "sortable": true
                  },
                  "slug": {
                    "type": "string",
                    "required": true,
                    "unique": true,
                    "sortable": true
                  },
                  "logo": {
                    "type": "string",
                    "required": false
                  },
                  "createdAt": {
                    "type": "date",
                    "required": true
                  },
                  "metadata": {
                    "type": "string",
                    "required": false
                  }
                }
              },
              "member": {
                "fields": {
                  "organizationId": {
                    "type": "string",
                    "required": true,
                    "references": {
                      "model": "organization",
                      "field": "id"
                    },
                    "index": true
                  },
                  "userId": {
                    "type": "string",
                    "required": true,
                    "references": {
                      "model": "user",
                      "field": "id"
                    },
                    "index": true
                  },
                  "role": {
                    "type": "string",
                    "required": true,
                    "sortable": true,
                    "defaultValue": "member"
                  },
                  "createdAt": {
                    "type": "date",
                    "required": true
                  }
                }
              },
              "invitation": {
                "fields": {
                  "organizationId": {
                    "type": "string",
                    "required": true,
                    "references": {
                      "model": "organization",
                      "field": "id"
                    },
                    "index": true
                  },
                  "email": {
                    "type": "string",
                    "required": true,
                    "sortable": true,
                    "index": true
                  },
                  "role": {
                    "type": "string",
                    "required": false,
                    "sortable": true
                  },
                  "status": {
                    "type": "string",
                    "required": true,
                    "sortable": true,
                    "defaultValue": "pending"
                  },
                  "expiresAt": {
                    "type": "date",
                    "required": true
                  },
                  "createdAt": {
                    "type": "date",
                    "required": true
                  },
                  "inviterId": {
                    "type": "string",
                    "references": {
                      "model": "user",
                      "field": "id"
                    },
                    "required": true
                  }
                }
              },
              "session": {
                "fields": {
                  "activeOrganizationId": {
                    "type": "string",
                    "required": false
                  }
                }
              }
            },
            "$Infer": {
              "Organization": {},
              "Invitation": {},
              "Member": {},
              "Team": {},
              "TeamMember": {},
              "ActiveOrganization": {}
            },
            "$ERROR_CODES": {
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_ORGANIZATION": "You are not allowed to create a new organization",
              "YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_ORGANIZATIONS": "You have reached the maximum number of organizations",
              "ORGANIZATION_ALREADY_EXISTS": "Organization already exists",
              "ORGANIZATION_SLUG_ALREADY_TAKEN": "Organization slug already taken",
              "ORGANIZATION_NOT_FOUND": "Organization not found",
              "USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION": "User is not a member of the organization",
              "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_ORGANIZATION": "You are not allowed to update this organization",
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_ORGANIZATION": "You are not allowed to delete this organization",
              "NO_ACTIVE_ORGANIZATION": "No active organization",
              "USER_IS_ALREADY_A_MEMBER_OF_THIS_ORGANIZATION": "User is already a member of this organization",
              "MEMBER_NOT_FOUND": "Member not found",
              "ROLE_NOT_FOUND": "Role not found",
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM": "You are not allowed to create a new team",
              "TEAM_ALREADY_EXISTS": "Team already exists",
              "TEAM_NOT_FOUND": "Team not found",
              "YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER": "You cannot leave the organization as the only owner",
              "YOU_CANNOT_LEAVE_THE_ORGANIZATION_WITHOUT_AN_OWNER": "You cannot leave the organization without an owner",
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_MEMBER": "You are not allowed to delete this member",
              "YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION": "You are not allowed to invite users to this organization",
              "USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION": "User is already invited to this organization",
              "INVITATION_NOT_FOUND": "Invitation not found",
              "YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION": "You are not the recipient of the invitation",
              "EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION": "Email verification required before accepting or rejecting invitation",
              "YOU_ARE_NOT_ALLOWED_TO_CANCEL_THIS_INVITATION": "You are not allowed to cancel this invitation",
              "INVITER_IS_NO_LONGER_A_MEMBER_OF_THE_ORGANIZATION": "Inviter is no longer a member of the organization",
              "YOU_ARE_NOT_ALLOWED_TO_INVITE_USER_WITH_THIS_ROLE": "You are not allowed to invite a user with this role",
              "FAILED_TO_RETRIEVE_INVITATION": "Failed to retrieve invitation",
              "YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_TEAMS": "You have reached the maximum number of teams",
              "UNABLE_TO_REMOVE_LAST_TEAM": "Unable to remove last team",
              "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_MEMBER": "You are not allowed to update this member",
              "ORGANIZATION_MEMBERSHIP_LIMIT_REACHED": "Organization membership limit reached",
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_TEAMS_IN_THIS_ORGANIZATION": "You are not allowed to create teams in this organization",
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_TEAMS_IN_THIS_ORGANIZATION": "You are not allowed to delete teams in this organization",
              "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_TEAM": "You are not allowed to update this team",
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_TEAM": "You are not allowed to delete this team",
              "INVITATION_LIMIT_REACHED": "Invitation limit reached",
              "TEAM_MEMBER_LIMIT_REACHED": "Team member limit reached",
              "USER_IS_NOT_A_MEMBER_OF_THE_TEAM": "User is not a member of the team",
              "YOU_CAN_NOT_ACCESS_THE_MEMBERS_OF_THIS_TEAM": "You are not allowed to list the members of this team",
              "YOU_DO_NOT_HAVE_AN_ACTIVE_TEAM": "You do not have an active team",
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM_MEMBER": "You are not allowed to create a new member",
              "YOU_ARE_NOT_ALLOWED_TO_REMOVE_A_TEAM_MEMBER": "You are not allowed to remove a team member",
              "YOU_ARE_NOT_ALLOWED_TO_ACCESS_THIS_ORGANIZATION": "You are not allowed to access this organization as an owner",
              "YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION": "You are not a member of this organization",
              "MISSING_AC_INSTANCE": "Dynamic Access Control requires a pre-defined ac instance on the server auth plugin. Read server logs for more information",
              "YOU_MUST_BE_IN_AN_ORGANIZATION_TO_CREATE_A_ROLE": "You must be in an organization to create a role",
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_ROLE": "You are not allowed to create a role",
              "YOU_ARE_NOT_ALLOWED_TO_UPDATE_A_ROLE": "You are not allowed to update a role",
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_A_ROLE": "You are not allowed to delete a role",
              "YOU_ARE_NOT_ALLOWED_TO_READ_A_ROLE": "You are not allowed to read a role",
              "YOU_ARE_NOT_ALLOWED_TO_LIST_A_ROLE": "You are not allowed to list a role",
              "YOU_ARE_NOT_ALLOWED_TO_GET_A_ROLE": "You are not allowed to get a role",
              "TOO_MANY_ROLES": "This organization has too many roles",
              "INVALID_RESOURCE": "The provided permission includes an invalid resource",
              "ROLE_NAME_IS_ALREADY_TAKEN": "That role name is already taken",
              "CANNOT_DELETE_A_PRE_DEFINED_ROLE": "Cannot delete a pre-defined role"
            }
          }
        },
        {
          "name": "sso",
          "config": {
            "id": "sso",
            "endpoints": {},
            "schema": {
              "ssoProvider": {
                "modelName": "ssoProvider",
                "fields": {
                  "issuer": {
                    "type": "string",
                    "required": true,
                    "fieldName": "issuer"
                  },
                  "oidcConfig": {
                    "type": "string",
                    "required": false,
                    "fieldName": "oidcConfig"
                  },
                  "samlConfig": {
                    "type": "string",
                    "required": false,
                    "fieldName": "samlConfig"
                  },
                  "userId": {
                    "type": "string",
                    "references": {
                      "model": "user",
                      "field": "id"
                    },
                    "fieldName": "userId"
                  },
                  "providerId": {
                    "type": "string",
                    "required": true,
                    "unique": true,
                    "fieldName": "providerId"
                  },
                  "organizationId": {
                    "type": "string",
                    "required": false,
                    "fieldName": "organizationId"
                  },
                  "domain": {
                    "type": "string",
                    "required": true,
                    "fieldName": "domain"
                  }
                }
              }
            }
          }
        }
      ]
    }
  }
}

Which area(s) are affected? (Select all that apply)

Backend

Auth config (if applicable)


Additional context

No response

Originally created by @ColaIan on GitHub (Dec 8, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/6609 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce Set ACS URL as callbackUrl ```ts sso({ defaultSSO: [ { domain: 'localhost:5173', // Your domain providerId: 'pydrox-dev', samlConfig: { // SP Configuration issuer: 'http://localhost:5173/api/auth/sso/saml2/sp/metadata', entryPoint: 'https://auth.pydrox.com/application/saml/development-better-auth-5173/sso/binding/redirect/', callbackUrl: 'http://localhost:5173/api/auth/sso/saml2/sp/acs/pydrox-dev', ... ``` Attempt to sign in with that SAML SSO provider ### Current vs. Expected behavior After base64decoding and xml deflating the SAMLRequest param, AssertionConsumerServiceURL shows empty string <img width="825" height="172" alt="Image" src="https://github.com/user-attachments/assets/2cdcf58f-30e4-4844-b313-89b296321e23" /> Expected value of AssertionConsumerServiceURL should be the value of callbackURL ### What version of Better Auth are you using? 1.4.5 ### System info ```bash { "system": { "platform": "linux", "arch": "arm64", "version": "#17~24.04.1-Ubuntu SMP Tue Nov 4 16:40:23 UTC 2025", "release": "6.14.0-1017-oracle", "cpuCount": 4, "cpuModel": "Neoverse-N1", "totalMemory": "23.42 GB", "freeMemory": "10.68 GB" }, "node": { "version": "v24.7.0", "env": "development" }, "packageManager": { "name": "bun", "version": "1.3.4" }, "frameworks": [ { "name": "svelte", "version": "^5.45.6" }, { "name": "@sveltejs/kit", "version": "^2.49.1" } ], "databases": [ { "name": "@prisma/client", "version": "^7.1.0" } ], "betterAuth": { "version": "^1.4.5", "config": { "experimental": { "joins": true }, "plugins": [ { "name": "organization", "config": { "id": "organization", "endpoints": {}, "schema": { "organization": { "fields": { "name": { "type": "string", "required": true, "sortable": true }, "slug": { "type": "string", "required": true, "unique": true, "sortable": true }, "logo": { "type": "string", "required": false }, "createdAt": { "type": "date", "required": true }, "metadata": { "type": "string", "required": false } } }, "member": { "fields": { "organizationId": { "type": "string", "required": true, "references": { "model": "organization", "field": "id" }, "index": true }, "userId": { "type": "string", "required": true, "references": { "model": "user", "field": "id" }, "index": true }, "role": { "type": "string", "required": true, "sortable": true, "defaultValue": "member" }, "createdAt": { "type": "date", "required": true } } }, "invitation": { "fields": { "organizationId": { "type": "string", "required": true, "references": { "model": "organization", "field": "id" }, "index": true }, "email": { "type": "string", "required": true, "sortable": true, "index": true }, "role": { "type": "string", "required": false, "sortable": true }, "status": { "type": "string", "required": true, "sortable": true, "defaultValue": "pending" }, "expiresAt": { "type": "date", "required": true }, "createdAt": { "type": "date", "required": true }, "inviterId": { "type": "string", "references": { "model": "user", "field": "id" }, "required": true } } }, "session": { "fields": { "activeOrganizationId": { "type": "string", "required": false } } } }, "$Infer": { "Organization": {}, "Invitation": {}, "Member": {}, "Team": {}, "TeamMember": {}, "ActiveOrganization": {} }, "$ERROR_CODES": { "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_ORGANIZATION": "You are not allowed to create a new organization", "YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_ORGANIZATIONS": "You have reached the maximum number of organizations", "ORGANIZATION_ALREADY_EXISTS": "Organization already exists", "ORGANIZATION_SLUG_ALREADY_TAKEN": "Organization slug already taken", "ORGANIZATION_NOT_FOUND": "Organization not found", "USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION": "User is not a member of the organization", "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_ORGANIZATION": "You are not allowed to update this organization", "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_ORGANIZATION": "You are not allowed to delete this organization", "NO_ACTIVE_ORGANIZATION": "No active organization", "USER_IS_ALREADY_A_MEMBER_OF_THIS_ORGANIZATION": "User is already a member of this organization", "MEMBER_NOT_FOUND": "Member not found", "ROLE_NOT_FOUND": "Role not found", "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM": "You are not allowed to create a new team", "TEAM_ALREADY_EXISTS": "Team already exists", "TEAM_NOT_FOUND": "Team not found", "YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER": "You cannot leave the organization as the only owner", "YOU_CANNOT_LEAVE_THE_ORGANIZATION_WITHOUT_AN_OWNER": "You cannot leave the organization without an owner", "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_MEMBER": "You are not allowed to delete this member", "YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION": "You are not allowed to invite users to this organization", "USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION": "User is already invited to this organization", "INVITATION_NOT_FOUND": "Invitation not found", "YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION": "You are not the recipient of the invitation", "EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION": "Email verification required before accepting or rejecting invitation", "YOU_ARE_NOT_ALLOWED_TO_CANCEL_THIS_INVITATION": "You are not allowed to cancel this invitation", "INVITER_IS_NO_LONGER_A_MEMBER_OF_THE_ORGANIZATION": "Inviter is no longer a member of the organization", "YOU_ARE_NOT_ALLOWED_TO_INVITE_USER_WITH_THIS_ROLE": "You are not allowed to invite a user with this role", "FAILED_TO_RETRIEVE_INVITATION": "Failed to retrieve invitation", "YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_TEAMS": "You have reached the maximum number of teams", "UNABLE_TO_REMOVE_LAST_TEAM": "Unable to remove last team", "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_MEMBER": "You are not allowed to update this member", "ORGANIZATION_MEMBERSHIP_LIMIT_REACHED": "Organization membership limit reached", "YOU_ARE_NOT_ALLOWED_TO_CREATE_TEAMS_IN_THIS_ORGANIZATION": "You are not allowed to create teams in this organization", "YOU_ARE_NOT_ALLOWED_TO_DELETE_TEAMS_IN_THIS_ORGANIZATION": "You are not allowed to delete teams in this organization", "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_TEAM": "You are not allowed to update this team", "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_TEAM": "You are not allowed to delete this team", "INVITATION_LIMIT_REACHED": "Invitation limit reached", "TEAM_MEMBER_LIMIT_REACHED": "Team member limit reached", "USER_IS_NOT_A_MEMBER_OF_THE_TEAM": "User is not a member of the team", "YOU_CAN_NOT_ACCESS_THE_MEMBERS_OF_THIS_TEAM": "You are not allowed to list the members of this team", "YOU_DO_NOT_HAVE_AN_ACTIVE_TEAM": "You do not have an active team", "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM_MEMBER": "You are not allowed to create a new member", "YOU_ARE_NOT_ALLOWED_TO_REMOVE_A_TEAM_MEMBER": "You are not allowed to remove a team member", "YOU_ARE_NOT_ALLOWED_TO_ACCESS_THIS_ORGANIZATION": "You are not allowed to access this organization as an owner", "YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION": "You are not a member of this organization", "MISSING_AC_INSTANCE": "Dynamic Access Control requires a pre-defined ac instance on the server auth plugin. Read server logs for more information", "YOU_MUST_BE_IN_AN_ORGANIZATION_TO_CREATE_A_ROLE": "You must be in an organization to create a role", "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_ROLE": "You are not allowed to create a role", "YOU_ARE_NOT_ALLOWED_TO_UPDATE_A_ROLE": "You are not allowed to update a role", "YOU_ARE_NOT_ALLOWED_TO_DELETE_A_ROLE": "You are not allowed to delete a role", "YOU_ARE_NOT_ALLOWED_TO_READ_A_ROLE": "You are not allowed to read a role", "YOU_ARE_NOT_ALLOWED_TO_LIST_A_ROLE": "You are not allowed to list a role", "YOU_ARE_NOT_ALLOWED_TO_GET_A_ROLE": "You are not allowed to get a role", "TOO_MANY_ROLES": "This organization has too many roles", "INVALID_RESOURCE": "The provided permission includes an invalid resource", "ROLE_NAME_IS_ALREADY_TAKEN": "That role name is already taken", "CANNOT_DELETE_A_PRE_DEFINED_ROLE": "Cannot delete a pre-defined role" } } }, { "name": "sso", "config": { "id": "sso", "endpoints": {}, "schema": { "ssoProvider": { "modelName": "ssoProvider", "fields": { "issuer": { "type": "string", "required": true, "fieldName": "issuer" }, "oidcConfig": { "type": "string", "required": false, "fieldName": "oidcConfig" }, "samlConfig": { "type": "string", "required": false, "fieldName": "samlConfig" }, "userId": { "type": "string", "references": { "model": "user", "field": "id" }, "fieldName": "userId" }, "providerId": { "type": "string", "required": true, "unique": true, "fieldName": "providerId" }, "organizationId": { "type": "string", "required": false, "fieldName": "organizationId" }, "domain": { "type": "string", "required": true, "fieldName": "domain" } } } } } } ] } } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript ``` ### Additional context _No response_
GiteaMirror added the locked label 2026-04-15 18:01:17 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Dec 8, 2025):

(tag @better-auth-agent if you would like an answer from the Agent)

If you need more help, tag @better-auth-agent in a comment so I can respond.

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3625964957 --> @better-auth-agent[bot] commented on GitHub (Dec 8, 2025): (tag @better-auth-agent if you would like an answer from the Agent) _If you need more help, tag @better-auth-agent in a comment so I can respond._ <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) • [Discord](https://discord.gg/fG2XXEuQX3) • [GitHub](https://github.com/Skyvern-AI/Skyvern) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/fG2XXEuQX3) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/Skyvern-AI/Skyvern)
Author
Owner

@dosubot[bot] commented on GitHub (Dec 8, 2025):

Hi @ColaIan! I'm Dosu and I’m helping the better-auth team.

This is now expected behavior in Better Auth v1.4.5 and later. A recent change standardized SAML ACS handling so that the AssertionConsumerServiceURL in the AuthnRequest is always set to the default pattern: ${baseURL}/sso/saml2/sp/acs/{providerId}. The value from samlConfig.callbackUrl is no longer used for this field, which explains why you see it empty or unexpected in the AuthnRequest XML. You should configure your Identity Provider (IdP) to use the default ACS URL for your providerId instead of relying on a custom callbackUrl. Custom ACS URLs are not currently supported for this field—if you need that, you may want to monitor future releases for changes to this behavior.
Details in PR #6069

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

<!-- gh-comment-id:3625984079 --> @dosubot[bot] commented on GitHub (Dec 8, 2025): <!-- Greeting --> Hi @ColaIan! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> This is now expected behavior in Better Auth v1.4.5 and later. A recent change standardized SAML ACS handling so that the AssertionConsumerServiceURL in the AuthnRequest is always set to the default pattern: `${baseURL}/sso/saml2/sp/acs/{providerId}`. The value from `samlConfig.callbackUrl` is no longer used for this field, which explains why you see it empty or unexpected in the AuthnRequest XML. You should configure your Identity Provider (IdP) to use the default ACS URL for your providerId instead of relying on a custom callbackUrl. Custom ACS URLs are not currently supported for this field—if you need that, you may want to monitor future releases for changes to this behavior. [Details in PR #6069](https://github.com/better-auth/better-auth/pull/6069) <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/ac3be08e-c8d5-46e2-adae-24a121dbfeaa?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/ac3be08e-c8d5-46e2-adae-24a121dbfeaa?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/ac3be08e-c8d5-46e2-adae-24a121dbfeaa?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/ac3be08e-c8d5-46e2-adae-24a121dbfeaa?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/ac3be08e-c8d5-46e2-adae-24a121dbfeaa?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/ac3be08e-c8d5-46e2-adae-24a121dbfeaa?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/ac3be08e-c8d5-46e2-adae-24a121dbfeaa?feedback_type=other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=This%20is%20now%20expected%20behavior%20in%20Better%20Auth%20v1.4.5%20and%20later.%20A%20recent%20change%20standardized%20SAML%20ACS%20handling%20so%20that%20the%20AssertionConsumerServiceURL%20in%20the%20AuthnRequest%20is%20always%20set%20to%20the%20default%20pattern%3A%20%60%24%7BbaseURL%7D/sso/saml2/sp/acs/%7BproviderId%7D%60.%20The%20value%20from%20%60samlConfig.callbackUrl%60%20is%20no%20longer%20used%20for%20this%20field%2C%20which%20explains%20why%20you%20see%20it%20empty%20or%20unexpected%20in%20the%20AuthnRequest%20XML.%20You%20should%20configure%20your%20Identity%20Provider%20%28IdP%29%20to%20use%20the%20default%20ACS%20URL%20for%20your%20providerId%20instead%20of%20relying%20on%20a%20custom%20callbackUrl.%20Custom%20ACS%20URLs%20are%20not%20currently%20supported%20for%20this%20field%E2%80%94if%20you%20need%20that%2C%20you%20may%20want%20to%20monitor%20future%20releases%20for%20changes%20to%20this%20behavior.%20%20%0A%5BDetails%20in%20PR%20%236069%5D%28https%3A//github.com/better-auth/better-auth/pull/6069%29)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/6609)
Author
Owner

@ColaIan commented on GitHub (Dec 8, 2025):

@dosu issue persists in 1.4.6-beta.3 where ACS URL is still left as empty string.

Additional reference:
Image
Normally the ACS URL should show between the word "of" and "doesn't" in this screenshot, now the two words stuck together

<!-- gh-comment-id:3626025657 --> @ColaIan commented on GitHub (Dec 8, 2025): @dosu issue persists in 1.4.6-beta.3 where ACS URL is still left as empty string. Additional reference: <img width="503" height="124" alt="Image" src="https://github.com/user-attachments/assets/35e04444-b5e7-4259-b86d-c999947430d6" /> Normally the ACS URL should show between the word "of" and "doesn't" in this screenshot, now the two words stuck together
Author
Owner

@dosubot[bot] commented on GitHub (Dec 8, 2025):

The ACS URL in the SAML AuthnRequest should never be empty in v1.4.6-beta.3 unless there’s a misconfiguration. The code always sets AssertionConsumerServiceURL to either your samlConfig.callbackUrl or, if that’s missing/empty, to the default pattern (${baseURL}/sso/saml2/sp/acs/{providerId})—unless you override spMetadata with a blob that leaves AssertionConsumerService empty.

To fix this, double-check that:

  • samlConfig.callbackUrl is a non-empty string,
  • providerId is set and matches your config,
  • you are not overriding spMetadata with incomplete or empty metadata.

If all of these are set correctly and you’re still seeing an empty ACS URL, it could indicate a bug in how the config is loaded or merged. You can review the relevant code and recent fixes for more details (PR #6069, PR #6118), and see the logic in the SSO plugin here.

If you’re still stuck after checking these, try logging the resolved samlConfig and providerId at runtime to confirm what’s actually being used to generate the SP metadata and AuthnRequest.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

<!-- gh-comment-id:3626052603 --> @dosubot[bot] commented on GitHub (Dec 8, 2025): <!-- Answer --> The ACS URL in the SAML AuthnRequest should never be empty in v1.4.6-beta.3 unless there’s a misconfiguration. The code always sets AssertionConsumerServiceURL to either your samlConfig.callbackUrl or, if that’s missing/empty, to the default pattern (${baseURL}/sso/saml2/sp/acs/{providerId})—unless you override spMetadata with a blob that leaves AssertionConsumerService empty. To fix this, double-check that: - samlConfig.callbackUrl is a non-empty string, - providerId is set and matches your config, - you are not overriding spMetadata with incomplete or empty metadata. If all of these are set correctly and you’re still seeing an empty ACS URL, it could indicate a bug in how the config is loaded or merged. You can review the relevant code and recent fixes for more details ([PR #6069](https://github.com/better-auth/better-auth/pull/6069), [PR #6118](https://github.com/better-auth/better-auth/pull/6118)), and see the logic in the SSO plugin [here](https://github.com/better-auth/better-auth/blob/e5909128ca7ecdf0ef68558ac7aa9888e7663233/packages/sso/src/routes/sso.ts#L76-L1935). If you’re still stuck after checking these, try logging the resolved samlConfig and providerId at runtime to confirm what’s actually being used to generate the SP metadata and AuthnRequest. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/441621c0-66a6-46a7-b64e-244a3d414241?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/441621c0-66a6-46a7-b64e-244a3d414241?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/441621c0-66a6-46a7-b64e-244a3d414241?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/441621c0-66a6-46a7-b64e-244a3d414241?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/441621c0-66a6-46a7-b64e-244a3d414241?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/441621c0-66a6-46a7-b64e-244a3d414241?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/441621c0-66a6-46a7-b64e-244a3d414241?feedback_type=other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=The%20ACS%20URL%20in%20the%20SAML%20AuthnRequest%20should%20never%20be%20empty%20in%20v1.4.6-beta.3%20unless%20there%E2%80%99s%20a%20misconfiguration.%20The%20code%20always%20sets%20AssertionConsumerServiceURL%20to%20either%20your%20samlConfig.callbackUrl%20or%2C%20if%20that%E2%80%99s%20missing/empty%2C%20to%20the%20default%20pattern%20%28%24%7BbaseURL%7D/sso/saml2/sp/acs/%7BproviderId%7D%29%E2%80%94unless%20you%20override%20spMetadata%20with%20a%20blob%20that%20leaves%20AssertionConsumerService%20empty.%20%0A%0ATo%20fix%20this%2C%20double-check%20that%3A%0A-%20samlConfig.callbackUrl%20is%20a%20non-empty%20string%2C%0A-%20providerId%20is%20set%20and%20matches%20your%20config%2C%0A-%20you%20are%20not%20overriding%20spMetadata%20with%20incomplete%20or%20empty%20metadata.%0A%0AIf%20all%20of%20these%20are%20set%20correctly%20and%20you%E2%80%99re%20still%20seeing%20an%20empty%20ACS%20URL%2C%20it%20could%20indicate%20a%20bug%20in%20how%20the%20config%20is%20loaded%20or%20merged.%20You%20can%20review%20the%20relevant%20code%20and%20recent%20fixes%20for%20more%20details%20%28%5BPR%20%236069%5D%28https%3A//github.com/better-auth/better-auth/pull/6069%29%2C%20%5BPR%20%236118%5D%28https%3A//github.com/better-auth/better-auth/pull/6118%29%29%2C%20and%20see%20the%20logic%20in%20the%20SSO%20plugin%20%5Bhere%5D%28https%3A//github.com/better-auth/better-auth/blob/e5909128ca7ecdf0ef68558ac7aa9888e7663233/packages/sso/src/routes/sso.ts%23L76-L1935%29.%0A%0AIf%20you%E2%80%99re%20still%20stuck%20after%20checking%20these%2C%20try%20logging%20the%20resolved%20samlConfig%20and%20providerId%20at%20runtime%20to%20confirm%20what%E2%80%99s%20actually%20being%20used%20to%20generate%20the%20SP%20metadata%20and%20AuthnRequest.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/6609)
Author
Owner

@ColaIan commented on GitHub (Dec 8, 2025):

@dosu how do I log samlConfig in runtime

<!-- gh-comment-id:3626561959 --> @ColaIan commented on GitHub (Dec 8, 2025): @dosu how do I log samlConfig in runtime
Author
Owner

@dosubot[bot] commented on GitHub (Dec 8, 2025):

You can log samlConfig at runtime by adding a log statement right after it’s parsed in your backend SSO/SAML handler. For example, in the sign-in or metadata endpoints, after you see code like:

const parsedSamlConfig = typeof provider.samlConfig === "object"
  ? provider.samlConfig
  : safeJsonParse<SAMLConfig>(provider.samlConfig as unknown as string);

Add:

console.log('samlConfig:', parsedSamlConfig);
// or if you prefer using the built-in logger:
ctx.context.logger.info('samlConfig:', parsedSamlConfig);

This will print the resolved samlConfig to your server logs whenever that endpoint is hit. You’ll find similar spots in the /sso/saml2/sp/metadata and /sso/saml2/sp/acs/:providerId endpoints where you can do the same thing. Remember to restart your backend after making code changes for the logs to appear as expected.
Reference

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

<!-- gh-comment-id:3626576430 --> @dosubot[bot] commented on GitHub (Dec 8, 2025): <!-- Answer --> You can log samlConfig at runtime by adding a log statement right after it’s parsed in your backend SSO/SAML handler. For example, in the sign-in or metadata endpoints, after you see code like: ```ts const parsedSamlConfig = typeof provider.samlConfig === "object" ? provider.samlConfig : safeJsonParse<SAMLConfig>(provider.samlConfig as unknown as string); ``` Add: ```ts console.log('samlConfig:', parsedSamlConfig); // or if you prefer using the built-in logger: ctx.context.logger.info('samlConfig:', parsedSamlConfig); ``` This will print the resolved samlConfig to your server logs whenever that endpoint is hit. You’ll find similar spots in the `/sso/saml2/sp/metadata` and `/sso/saml2/sp/acs/:providerId` endpoints where you can do the same thing. Remember to restart your backend after making code changes for the logs to appear as expected. [Reference](https://github.com/better-auth/better-auth/blob/e5909128ca7ecdf0ef68558ac7aa9888e7663233/packages/sso/src/routes/sso.ts#L51-L2180) <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/35bf3d84-a9da-486c-8e2f-db727a0b9e3c?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/35bf3d84-a9da-486c-8e2f-db727a0b9e3c?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/35bf3d84-a9da-486c-8e2f-db727a0b9e3c?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/35bf3d84-a9da-486c-8e2f-db727a0b9e3c?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/35bf3d84-a9da-486c-8e2f-db727a0b9e3c?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/35bf3d84-a9da-486c-8e2f-db727a0b9e3c?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/35bf3d84-a9da-486c-8e2f-db727a0b9e3c?feedback_type=other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=You%20can%20log%20samlConfig%20at%20runtime%20by%20adding%20a%20log%20statement%20right%20after%20it%E2%80%99s%20parsed%20in%20your%20backend%20SSO/SAML%20handler.%20For%20example%2C%20in%20the%20sign-in%20or%20metadata%20endpoints%2C%20after%20you%20see%20code%20like%3A%0A%0A%60%60%60ts%0Aconst%20parsedSamlConfig%20%3D%20typeof%20provider.samlConfig%20%3D%3D%3D%20%22object%22%0A%20%20%3F%20provider.samlConfig%0A%20%20%3A%20safeJsonParse%3CSAMLConfig%3E%28provider.samlConfig%20as%20unknown%20as%20string%29%3B%0A%60%60%60%0A%0AAdd%3A%0A%0A%60%60%60ts%0Aconsole.log%28%27samlConfig%3A%27%2C%20parsedSamlConfig%29%3B%0A//%20or%20if%20you%20prefer%20using%20the%20built-in%20logger%3A%0Actx.context.logger.info%28%27samlConfig%3A%27%2C%20parsedSamlConfig%29%3B%0A%60%60%60%0A%0AThis%20will%20print%20the%20resolved%20samlConfig%20to%20your%20server%20logs%20whenever%20that%20endpoint%20is%20hit.%20You%E2%80%99ll%20find%20similar%20spots%20in%20the%20%60/sso/saml2/sp/metadata%60%20and%20%60/sso/saml2/sp/acs/%3AproviderId%60%20endpoints%20where%20you%20can%20do%20the%20same%20thing.%20Remember%20to%20restart%20your%20backend%20after%20making%20code%20changes%20for%20the%20logs%20to%20appear%20as%20expected.%20%20%0A%5BReference%5D%28https%3A//github.com/better-auth/better-auth/blob/e5909128ca7ecdf0ef68558ac7aa9888e7663233/packages/sso/src/routes/sso.ts%23L51-L2180%29)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/6609)
Author
Owner

@ColaIan commented on GitHub (Dec 8, 2025):

The issue was parsedSamlConfig.spMetadata.metadata overriding the saml.SPMeta code that reads callbackurl/constructs ACS URL, closing invalid issue

Though now if I leave out metadata, it says ERROR [Better Auth]: TypeError TypeError: saml.SPMetadata is not a function, will open a separate issue if that is actually an issue.

<!-- gh-comment-id:3626737288 --> @ColaIan commented on GitHub (Dec 8, 2025): The issue was parsedSamlConfig.spMetadata.metadata overriding the saml.SPMeta code that reads callbackurl/constructs ACS URL, closing invalid issue Though now if I leave out metadata, it says `ERROR [Better Auth]: TypeError TypeError: saml.SPMetadata is not a function`, will open a separate issue if that is actually an issue.
Author
Owner

@rphlmr commented on GitHub (Jan 22, 2026):

Hi @ColaIan do you still have ERROR [Better Auth]: TypeError TypeError: saml.SPMetadata is not a function? I have this issue too and I was wondering if it’s not because of the import:

https://github.com/better-auth/better-auth/blob/87777cdcd0d9a4405ce668130eeeba6ea4566cf9/packages/sso/src/routes/sso.ts#L22

I guess it should be import saml from "samlify";

@dosu what do you think?

<!-- gh-comment-id:3786063676 --> @rphlmr commented on GitHub (Jan 22, 2026): Hi @ColaIan do you still have `ERROR [Better Auth]: TypeError TypeError: saml.SPMetadata is not a function`? I have this issue too and I was wondering if it’s not because of the import: [https://github.com/better-auth/better-auth/blob/87777cdcd0d9a4405ce668130eeeba6ea4566cf9/packages/sso/src/routes/sso.ts#L22](https://github.com/better-auth/better-auth/blob/87777cdcd0d9a4405ce668130eeeba6ea4566cf9/packages/sso/src/routes/sso.ts#L22) I guess it should be `import saml from "samlify";` @dosu what do you think?
Author
Owner

@github-actions[bot] commented on GitHub (Apr 1, 2026):

This issue has been locked as it was closed more than 7 days ago. If you're experiencing a similar problem or you have additional context, please open a new issue and reference this one.

<!-- gh-comment-id:4166563467 --> @github-actions[bot] commented on GitHub (Apr 1, 2026): This issue has been locked as it was closed more than 7 days ago. If you're experiencing a similar problem or you have additional context, please open a new issue and reference this one.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#19201