From aaa007f005745f7f8e4e981e8b94b37802121174 Mon Sep 17 00:00:00 2001 From: Kiran Date: Mon, 21 Feb 2022 11:22:12 +0530 Subject: [PATCH] Create saml response --- README.md | 4 ++- lib/env.ts | 2 +- pages/api/saml/sso.ts | 48 ++++++++++++++++++++++++++++ pages/saml/sso.tsx | 21 ++---------- utils/index.ts | 74 ++++++++++++++++++++++++------------------- 5 files changed, 96 insertions(+), 53 deletions(-) create mode 100644 pages/api/saml/sso.ts diff --git a/README.md b/README.md index 73221e1..4e27616 100644 --- a/README.md +++ b/README.md @@ -11,4 +11,6 @@ // Validate the SAMLRequest // Create SAMLResponse // POST the SAMLResponse to ACS URL -// Remove the RelayState from the session \ No newline at end of file +// Remove the RelayState from the session + +https://localhost:4000/api/saml/sso?RelayState=boxyhq_jackson_baf6be45ae7f5fe53a005718f941a13e&SAMLRequest=nVXRkqI4FP0VCx8thURbhVKnsOlubVFR0VZftiIEiB0SJUHUrx%2FUtdetnZmd3bdUOPecc0%2BSS%2BvbMaaFA04E4aytgIqmfOu0BIrpzjBTGbEp3qdYyEIOY8K4fmgracIMjgQRBkMxFob0jJk5tA1Y0YxdwiX3OFUKfaut%2FLHRNQ%2FAQNObgY70Rs3HzYZSWNwF84ocKESK%2B0xIxGS%2BpUFY1mAZAlerGVrVAPVKE4K1UnD%2BpO4S5hMW%2FtrH5gYSRs91nbIznrlKwRQCJzIXfuZMpDFOZjg5EA%2FPp3ZbiaTcGapKuYdoxIU0qpqmqUJwFXniKn4gPk5GuVJb6fLjqTdRblEZ1waSh4x%2BbQ3dbSidi6jIVS9FlU1OGu0rHo9b6gNvpzUjIUMyTfBN4strlmWVrFrhSajCi1lNV3OAL0hYVG5V2O%2BzgHdaz4hxRvLOyBldlIdYRtwvmDTkCZFR%2FBNKoALtQlnGR6%2FsgRorKuqDnd9m0Wp3Y%2BWYJ7iYCFQWEYJP9QvfFAc4wczDhfm031aKP740nZabICYCnsTiYf2v6n%2BLBbMDpnyH%2FbK4N3Fx8PtsP05EfbRmkTB%2FMf8xm7z94l%2BJ3CgWiKa4s4sb%2FZW7%2BPRDOmTvq0VvuY4%2B6TsdkE3Y3NZWAZx61mgfx4tJu6U%2BVrbUr2Tz9eN1%2BDrBGzB4eZuHcaS%2FTO0F%2BISzTCxHtWA4Wa8ndu%2FsepFlUmibNZsKxx9szvpHyvzNfPZiisnYXgMYIf%2Ftie7GDgh7Vhb4cUitLQAyhQtg7atHKdwqXPTTM5zr9cFczeqr8WZXGwBzt946%2B%2FnMtdbAs7xDSaOwWn19ZyF00Wl2BNECqUkwxdHi4DDW%2F%2BADa1PC20kXhvOoXtvLaB4dtabbJVa1kYk9eV27p9VJP0U20MzD%2BEjny9IbOdmWP172MR090W6zC%2B3JMOrCrK%2FbQ1y1PrYlFWSzl7HVtKZhdABxF%2BQ57fkSxKXhp3w%2B78mIjEofvV5tGTUHOy1rOtsAkbS7jpf2myO9szgfwdJtqOe0NCNHB1TNdvsW%2BkPQDxv3KXuZJ33L4ZR4p%2F8zZV%2FzS4fkz9GgAq47xC8HV6iBY0So6fsJFvlcMynl2XOCkczHmkzS63tQ%2F%2FkH6HwH \ No newline at end of file diff --git a/lib/env.ts b/lib/env.ts index 512e57c..31ca913 100644 --- a/lib/env.ts +++ b/lib/env.ts @@ -1,6 +1,6 @@ const appUrl = process.env.APP_URL || 'http://localhost:4000'; const entityId = process.env.ENTITY_ID || 'http://saml.example.com'; -const ssoUrl = `${appUrl}/saml/sso`; +const ssoUrl = `${appUrl}/api/saml/sso`; export default { appUrl, diff --git a/pages/api/saml/sso.ts b/pages/api/saml/sso.ts new file mode 100644 index 0000000..eace963 --- /dev/null +++ b/pages/api/saml/sso.ts @@ -0,0 +1,48 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import { createResponseForm, createSAMLResponseXML } from 'utils'; +import { User } from 'types'; +import config from '../../../lib/env' + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + + switch (req.method) { + case 'GET': + return await processSAMLRequest(); + default: + return res.status(405).end(`Method ${req.method} Not Allowed`); + } + + async function processSAMLRequest() { + const relayState = req.query.RelayState; + const samlRequest = req.query.SAMLRequest; + + const idpIdentityId = config.entityId; + const audience = config.entityId; + const acsUrl = 'http://localhost:3000/sso/acs'; // TODO: Fetch acsUrl from SAMLRequest + + const user: User = { + id: '1', + email: 'kiran@boxyhq.com', + firstName: 'Kiran', + lastName: 'K', + }; + + const xml = await createSAMLResponseXML({ + idpIdentityId: idpIdentityId, + audience: audience, + acsUrl: acsUrl, + user: user, + }); + + console.log(xml) + + const encodedSamlResponse = Buffer.from(xml).toString('base64'); + + const html = createResponseForm(relayState, encodedSamlResponse, acsUrl); + + res.send(html); + } +} \ No newline at end of file diff --git a/pages/saml/sso.tsx b/pages/saml/sso.tsx index fd40edb..1143e66 100644 --- a/pages/saml/sso.tsx +++ b/pages/saml/sso.tsx @@ -1,23 +1,7 @@ import type { GetServerSideProps } from 'next'; import React from "react"; import { AuthNRequest } from '../../types' -import { extractSAMLRequestAttributes, createSAMLResponse } from '../../utils' - -export const getServerSideProps: GetServerSideProps = async ({query, params}) => { - const relayState = query.RelayState as string; - const samlRequest = query.SAMLRequest as string; - - console.log(await createSAMLResponse()) - - const attributes = await extractSAMLRequestAttributes(samlRequest); - - return { - props: { - relayState, - samlRequest, - }, - } -} +import { extractSAMLRequestAttributes, createResponseForm } from '../../utils' const ProcessRequest: React.FC = ({relayState, samlRequest}) => { return ( @@ -25,4 +9,5 @@ const ProcessRequest: React.FC = ({relayState, samlRequest}) => { ); } -export default ProcessRequest; \ No newline at end of file +export default ProcessRequest; + diff --git a/utils/index.ts b/utils/index.ts index 228a127..cd36432 100644 --- a/utils/index.ts +++ b/utils/index.ts @@ -67,18 +67,13 @@ const extractCert = (certificate: string) => { .trim(); }; -// Create SAMLResponse -const createSAMLResponse = async (): Promise => { - const idpIdentityId = 'urn:dev-tyj7qyzz.auth0.com'; - const audience = 'https://saml.boxyhq.com'; - const acsUrl = 'http://localhost:3000/sso/acs'; - - const user: User = { - id: '1', - email: 'kiran@boxyhq.com', - firstName: 'Kiran', - lastName: 'K', - } +const createSAMLResponseXML = async (params: { + idpIdentityId: string, + audience: string, + acsUrl: string, + user: User +}): Promise => { + const {idpIdentityId, audience, acsUrl, user} = params; const nodes = { 'samlp:Response':{ @@ -136,24 +131,36 @@ const createSAMLResponse = async (): Promise => { 'saml:AttributeStatement': { '@xmlns:xs': 'http://www.w3.org/2001/XMLSchema', '@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance', - 'saml:Attribute': { - '@Name': 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress', - '@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri', - 'saml:AttributeValue': { - '@xsi:type': 'xs:string', - '#text': user.email, - } - }, - - // @ts-ignore - 'saml:Attribute': { - '@Name': 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress', - '@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri', - 'saml:AttributeValue': { - '@xsi:type': 'xs:string', - '#text': user.id - } - }, + 'saml:Attribute': [ + { + '@Name': 'id', + '@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', + 'saml:AttributeValue': { + '#text': user.id, + } + }, + { + '@Name': 'email', + '@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', + 'saml:AttributeValue': { + '#text': user.email, + } + }, + { + '@Name': 'firstName', + '@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', + 'saml:AttributeValue': { + '#text': user.firstName, + } + }, + { + '@Name': 'lastName', + '@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', + 'saml:AttributeValue': { + '#text': user.lastName, + } + }, + ] } } } @@ -162,7 +169,8 @@ const createSAMLResponse = async (): Promise => { return xmlbuilder.create(nodes).end({ pretty: true}); }; -export const createResponseForm = (relayState: string, samlResponse: string, acsUrl: string) => { +// Create the HTML form to submit the response +export const createResponseForm = (relayState: string, encodedSamlResponse: string, acsUrl: string) => { const formElements = [ '', '', @@ -176,7 +184,7 @@ export const createResponseForm = (relayState: string, samlResponse: string, acs '', '
', '', - '', + '', '', '
', '', @@ -191,7 +199,7 @@ export { parseXML, extractSAMLRequestAttributes, createIdPMetadataXML, - createSAMLResponse, + createSAMLResponseXML, createCertificate, extractCert, }; \ No newline at end of file