I am trying to generate an OAuth token with the SAML 2.0 Bearer Assertion flow in Salesforce.com, but receiving invalid assertion error:
{
"error_description": "invalid assertion",
"error": "invalid_grant"
}
The assertion that I am generating is:
<?xml version="1.0" encoding="UTF-8"?>
<saml:Assertion
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Version="2.0" ID="_S1OBTt6ZmtbsK32e6nMf02kNubxIvCv4" IssueInstant="2015-04-12T15:33:26.414Z">
<saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">3MVG9Y...1wpi
</saml:Issuer>
<Signature
xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<Reference URI="#_S1OBTt6ZmtbsK32e6nMf02kNubxIvCv4">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<DigestValue>zOTAJJZUy/gT0c6Bpsbc5sArQ9xeJToVP1zQe/SS0FA=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>Ne35NiqGR01VxqxTxzJ...sPn8roB/TZZZmyHA==</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>MIIEjzCCA3egAwIBAgIJAPmw2...2XgjN09asKQ30dARPEzdQ==</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>
<saml:Subject
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">subhash.bhushan@kaybus.com
</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
<saml:SubjectConfirmationData NotOnOrAfter="2015-04-12T15:34:26.414Z" Recipient="https://subhash-kaybus-dev-ed.my.salesforce.com/services/oauth2/token"/>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" NotBefore="2015-04-12T15:33:26.414Z" NotOnOrAfter="2015-04-12T15:34:26.414Z">
<saml:AudienceRestriction
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
<saml:Audience>https://subhash-kaybus-dev-ed.my.salesforce.com/services/oauth2/token</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement AuthnInstant="2015-04-12T15:33:26.414Z"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
<saml:AuthnContext
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
</saml:Assertion>
I generated a X509 cert locally using the following command, and attached it to my connected app:
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 999
The node.js program I am using to generate the assertion is below:
var fs = require('fs');
var pd = require('pretty-data').pd;
var saml20 = require('saml').Saml20;
var request = require('request');
var base64url = require('base64url');
var xmldom = require('xmldom');
var options = {
cert: fs.readFileSync(__dirname + '/cert.pem'),
key: fs.readFileSync(__dirname + '/key.pem'),
issuer: '3MVG9Y6d_Btp4...apXfJW1wpi',
lifetimeInSeconds: 60,
audiences: 'https://subhash-kaybus-dev-ed.my.salesforce.com/services/oauth2/token',
recipient: 'https://subhash-kaybus-dev-ed.my.salesforce.com/services/oauth2/token',
// attributes: {
// 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress': 'sumukhavadhani@gmail.com',
// 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name': 'Sumukh Avadhani'
// },
nameIdentifier: 'subhash.bhushan@kaybus.com',
nameIdentifierFormat: 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified'
};
var signedAssertion = saml20.create(options);
request({
url: 'https://subhash-kaybus-dev-ed.my.salesforce.com/services/oauth2/token',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
rejectUnauthorized:false,
form: {
grant_type: 'urn:ietf:params:oauth:grant-type:saml2-bearer',
assertion: base64url.encode(signedAssertion)
}
}, function(err, body, response) {
if(err) {
cb(err)
} else {
console.log('body = \n', pd.json(body["body"]));
}
});
I am using a free developer salesforce account to do this testing.
I have confirmed that the client ID being mentioned here is the consumer key of my connected app, and the entity ID mentioned in my connected app is the same I am using as token end-point in the assertion. I have enabled all scopes for my connected app, relaxed IP restrictions, created a permission set and added my user to it.
I have gone through the assertion with a fine-toothed comb and compared it line-by-line to the example mentioned in https://help.salesforce.com/HTViewHelpDoc?id=remoteaccess_oauth_SAML_bearer_flow.htm&language=en_US and ensure they match up. Amazingly, when I fire the assertion using a curl command with the Base64URL data:
curl -X POST https://test.salesforce.com/services/oauth2/token -d "grant_type=urn:ietf:params:oauth:grant-type:saml2-bearer" -d "assertion=PD94bWwgdmVy..."
I first get the following error:
{"error_description":"invalid assertion","error":"invalid_grant"}
After a minute, I get:
{"error_description":"expired authorization code","error":"invalid_grant"}
So I guess the assertion is being decoded alright by salesforce.
I am using the generated OAuth token to make subsequent API calls to Salesforce through a canvas app. Is there some setup I should ensure on my connected app?
What am I doing wrong? Any help will be appreciated.