tngan / samlify

Node.js library for SAML SSO
https://samlify.js.org
MIT License
609 stars 217 forks source link

ERR_INVALID_ARG_TYPE in acs (Sveltekit) #527

Closed proflivio closed 11 months ago

proflivio commented 11 months ago

I'm new to this SSO thing, so maybe I got it all wrong, I apologize in advance if that's the case. I'm trying to implement it in Sveltekit, if that's possible at all.

Trying to login to AAI@EduHr (the Croatian academic network SSO) I get a request full of undefineds (body stream, baseUrl, and such). The login request works fine, but after entering my credentials I get the invalid arg type error in acs.

Here are the idp metadata, and below are my sp's:

<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
    xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui"
    xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="http://localhost:5173/sso/metadata">
    <md:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
        <md:KeyDescriptor>
            <ds:KeyInfo>
                <ds:X509Data>
                    <ds:X509Certificate>${SSO_CERT}</ds:X509Certificate>
                </ds:X509Data>
            </ds:KeyInfo>
        </md:KeyDescriptor>
        <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:5173/sso/logout"/>
        <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost:5173/sso/acs" index="1"/>
    </md:SPSSODescriptor>
    ...
</md:EntityDescriptor>

Here's the code for my acs endpoint:

import { error, redirect } from '@sveltejs/kit'
import { sp, idp } from '../config'

export const POST = async ({ request }) => {
    try {
        const response = await sp.parseLoginResponse(idp, 'post', request)
        console.log(response)
        throw redirect(302, '/')
    } catch (e) {
        console.log(e)
        throw error(500, 'Error')
    }
    throw redirect(302, '/')
}

Error log:

TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined
    at new NodeError (node:internal/errors:399:5)
    at Function.from (node:buffer:339:9)
    at base64Decode (/home/livio/dev/aaiedu/node_modules/.pnpm/samlify@2.8.10/node_modules/samlify/build/src/utility.js:126:24)
    at /home/livio/dev/aaiedu/node_modules/.pnpm/samlify@2.8.10/node_modules/samlify/build/src/flow.js:190:69
    at step (/home/livio/dev/aaiedu/node_modules/.pnpm/samlify@2.8.10/node_modules/samlify/build/src/flow.js:33:23)
    at Object.next (/home/livio/dev/aaiedu/node_modules/.pnpm/samlify@2.8.10/node_modules/samlify/build/src/flow.js:14:53)
    at /home/livio/dev/aaiedu/node_modules/.pnpm/samlify@2.8.10/node_modules/samlify/build/src/flow.js:8:71
    at new Promise (<anonymous>)
    at __awaiter (/home/livio/dev/aaiedu/node_modules/.pnpm/samlify@2.8.10/node_modules/samlify/build/src/flow.js:4:12)
    at postFlow (/home/livio/dev/aaiedu/node_modules/.pnpm/samlify@2.8.10/node_modules/samlify/build/src/flow.js:181:12) {
  code: 'ERR_INVALID_ARG_TYPE'
}
tngan commented 11 months ago

@proflivio You may want to check what is inside request. It is the raw request body coming from your idp.

await sp.parseLoginResponse(idpNoEncrypt, 'post', { body: { SAMLResponse } });
proflivio commented 11 months ago

@tngan, do you mean the string that begins with 'SAMLResponse=...'?

My bad, I was trying to use the Sveltekit request, but when I use the raw request body, I get

Error: ERR_UNDEFINED_STATUS
    at checkStatus (/home/livio/dev/aaiedu/node_modules/.pnpm/samlify@2.8.10/node_modules/samlify/build/src/flow.js:380:15)
    at /home/livio/dev/aaiedu/node_modules/.pnpm/samlify@2.8.10/node_modules/samlify/build/src/flow.js:206:42
    at step (/home/livio/dev/aaiedu/node_modules/.pnpm/samlify@2.8.10/node_modules/samlify/build/src/flow.js:33:23)
    at Object.next (/home/livio/dev/aaiedu/node_modules/.pnpm/samlify@2.8.10/node_modules/samlify/build/src/flow.js:14:53)
    at fulfilled (/home/livio/dev/aaiedu/node_modules/.pnpm/samlify@2.8.10/node_modules/samlify/build/src/flow.js:5:58)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

I set the schema validator to skip validation for now, because I don't know exactly what attributes to expect yet (the docs aren't very specific about that).

tngan commented 11 months ago
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_8e8dc5f69a98cc4c1ff3427e5ce34606fd672f91e6" Version="2.0" IssueInstant="2014-07-17T01:01:48Z" Destination="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685">
  <saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer>
  <samlp:Status>
    <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
  </samlp:Status>

@proflivio This is an example for success SAML response. Please check your actual response sent from IdP whether Response > Status > StatusCode is urn:oasis:names:tc:SAML:2.0:status:Success.

proflivio commented 11 months ago

@tngan It turned out it was a base64 encoded response, so after decoding it, I got this:

<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_439e548918e6f0765e9b2a5dc706c20f8178cad368" Version="2.0" IssueInstant="2023-10-31T13:11:26Z" Destination="http://localhost:5173/sso/acs" InResponseTo="_d4b28460-e3db-4884-9ede-45f2a5fb38eb">
    <saml:Issuer>https://fed-lab.aaiedu.hr/sso/saml2/idp/metadata.php</saml:Issuer>
    <samlp:Status>
        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
    </samlp:Status>
    <saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="_5eaa9863eaec93cefdf00274f810a2bb2031816204" Version="2.0" IssueInstant="2023-10-31T13:11:26Z">
        <saml:Issuer>https://fed-lab.aaiedu.hr/sso/saml2/idp/metadata.php</saml:Issuer>
        <saml:Subject>
            <saml:NameID NameQualifier="https://fed-lab.aaiedu.hr/sso/saml2/idp/metadata.php" SPNameQualifier="http://localhost:5173/sso/metadata" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">liviohubicka@aai-test.hr</saml:NameID>
            <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
                <saml:SubjectConfirmationData NotOnOrAfter="2023-10-31T13:16:26Z" Recipient="http://localhost:5173/sso/acs" InResponseTo="_d4b28460-e3db-4884-9ede-45f2a5fb38eb"/>
            </saml:SubjectConfirmation>
        </saml:Subject>
        <saml:Conditions NotBefore="2023-10-31T13:10:56Z" NotOnOrAfter="2023-10-31T13:16:26Z">
            <saml:AudienceRestriction>
                <saml:Audience>http://localhost:5173/sso/metadata</saml:Audience>
            </saml:AudienceRestriction>
        </saml:Conditions>
        <saml:AuthnStatement AuthnInstant="2023-10-31T12:45:09Z" SessionNotOnOrAfter="2023-10-31T20:45:09Z" SessionIndex="_f79c7ac32f3f8cab16403e18864bc1c32817f3ea23">
            <saml:AuthnContext>
                <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
            </saml:AuthnContext>
        </saml:AuthnStatement>
        <saml:AttributeStatement>
            <saml:Attribute Name="hrEduOrgURL" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
                <saml:AttributeValue xsi:type="xs:string">http://www.aai-test.hr/</saml:AttributeValue>
            </saml:Attribute>
            ...
        </saml:AttributeStatement>
    </saml:Assertion>
</samlp:Response>

... but parsing this response throws an error:

Error: ERR_ZERO_SIGNATURE
    at Object.verifySignature (/home/livio/dev/aaiedu/node_modules/.pnpm/samlify@2.8.10/node_modules/samlify/build/src/libsaml.js:319:23)
    at /home/livio/dev/aaiedu/node_modules/.pnpm/samlify@2.8.10/node_modules/samlify/build/src/flow.js:232:55
    at step (/home/livio/dev/aaiedu/node_modules/.pnpm/samlify@2.8.10/node_modules/samlify/build/src/flow.js:33:23)
    at Object.next (/home/livio/dev/aaiedu/node_modules/.pnpm/samlify@2.8.10/node_modules/samlify/build/src/flow.js:14:53)
    at fulfilled (/home/livio/dev/aaiedu/node_modules/.pnpm/samlify@2.8.10/node_modules/samlify/build/src/flow.js:5:58)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

What am I still doing wrong?

Edit: It has now occured to me samlify needs responses to be signed. Should I set WantAssertionsSigned in my metadata to true?

proflivio commented 11 months ago

@tngan Assertion signing solved my issue, thank you for your help.

I'm closing the issue leaving this for future reference, if someone stumbles on the same problem.

const raw = (await request.text()).replace('SAMLResponse=', '')
const SAMLResponse = decodeURIComponent(raw)

try {
    const parsedResponse = await sp.parseLoginResponse(idp, 'post', { body: { SAMLResponse } })
}
...