Gigya Job Openings

Signing Requests to SAP Customer Data Cloud

Skip to end of metadata
Go to start of metadata

Overview

Most REST API requests to Customer Data Cloud should be made securely, using an authentication mechanism. The recommended method is to use a bearer token, constructed using a unique RSA key provided on the SAP Customer Data Cloud console. Another way is to use an application key and secret (or user key and secret). This is true also for requests made on your behalf (using your API Key) by third parties.

Both methods require creating an application on the Console, that is associated with a permission group.

Creating and Managing Applications

You can create multiple applications, each with its own permissions, and give groups of users access to these various applications. 

To create your SAP Customer Data Cloud Applications:

  1. Login to the Console.
  2. Navigate to the Admin tab.
  3. Select Applications.
  4. Once on the Applications page, press Create New Application, give the application a name and choose the permission group to which it is associated, then click Create.

  5. In the Create New Application window, copy the RSA Private Key, paste it into a document and save that document securely. This is used for signing requests with a bearer token. 

    The RSA Private Key is only available inside the pop-up modal when the application is created. We do not store the RSA secret, if you do not copy it prior to closing the window, it will be necessary to generate a new key-pair. See Using an RSA Key for more information.

Managing Applications

  •  Once the app is created you can view the Apps userKey and secret by clicking the Edit icon, which will take you to the apps Edit Application page.
  • You can disseminate the user key and secret to give users the privileges associated with this app. Users will use the key and secret in requests to SAP Customer Data Cloud.
  • To revoke access to the application:
    • When using a user key and secret: simply delete the application. All attempts to use this key and secret will fail.
    • When using the RSA key: generate a new key. This immediately invalidates the old key. 

 

Adding An Application Key

You can add existing applications similar to creating a new application. Simply click the Add Existing Application button and enter the userKey associated with the application, select a Permissions group to the application and press Add. If the import was successful you will get a notification (with the name of the application as it exists in the parent account):

 

It is important to note that the data associated with applications are per API key and will show blank if attempting to Edit them while viewing the Admin tab from a different API key.

RSA Keys

When creating a new application in the Console, an RSA key-pair is assigned to the application. Copy this key and store it securely. 

The RSA Private Key is only available inside the pop-up modal when the application is created. We do not store the RSA key, so if you lose it, you will need to generate a new key-pair with a new Private key.

 

You can sign an API request to SAP Customer Data Cloud using an HTTP bearer token. This replaces the application / user key and secret signature method. To do so, you should create a JWT based on the following structure, then hash the JWT using the RSA key to create a bearer token, and sign the API request using that token, following the steps below: 

JWT Header and Body

Construct a JWT in the following structure: 

Header

{   
	"alg": "RS256",   
	"typ": "JWT",   
	"kid": "Udfahjger6=io" 
}

Body:

{   
	"iat": 1548189000,   
	"jti": "b1cad719-e901-4699-9a9a-a20c96439603", 
} 


Explanation of Fields

FieldDescription
algThe JWT signing algorithm. Must be "RS256".
typType, should always be "JWT".
kidThe user or application key associated with the private key used for signing the token.
iatTimestamp that identifies when the JWT was issued. SAP Customer Data Cloud uses this for invalidating requests that were made before the "iat" or a reasonable time after.
jti(Optional) A nonce. When used, this JWT may be used only once.

 

Hashing the JWT

Hash the JWT as follows: 

  1. Use base-64 encoding to convert the header and body JSONs (separately) into strings. 
  2. Use your private key together with an RS256 hashing algorithm, and with the header and body strings, to create a signature
  3. Construct a bearer token made up of the 3 base-64 encoded strings, separated by a period, in the following structure: 

    [header].[body].[signature]

Signing API Requests

When making REST API calls, sign them by passing an "Authorization" key in the HTTP header, with the bearer token passed as the value, in the following way: 

Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ilt1c2Vya2V5XSJ9.eyJpYXQiOjE1NDgxOsdkj 

 

For additional information on Bearer tokens, see https://tools.ietf.org/html/rfc6750

Expand the link below for an example implementation using C#.

 Expand for .NET code example
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net.Http;
using System.Security.Claims;
using System.Threading.Tasks;
using demo_site.Models;
using demo_site.Utils;
using JsonDiffPatchDotNet;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace demo_site.Controllers
{
    public class HomeController : Controller
    {
        public static readonly string apikey = "<Gigya/CDC-API-Key>";
        public static HttpClient _httpClient = new HttpClient();

        private string key = @"-----BEGIN RSA PRIVATE KEY-----
xxxxxxxxxxXXXXXXXXXXxxxxxxxxxXXxxXxXxxXxxxXXXXxxxxXxXxXxxxXXXxxX
yyYYydyyasuyydgde32gyguygyugy3g4gug24ygy3g3ug4y42uy5g6yg4uyggryh
xxxxxxxxxxXXXXXXXXXXxxxxxxxxxXXxxXxXxxXxxxXXXXxxxxXxXxXxxxXXXxxX
yyYYydyyasuyydgde32gyguygyugy3g4gug24ygy3g3ug4y42uy5g6yg4uyggryh
xxxxxxxxxxXXXXXXXXXXxxxxxxxxxXXxxXxXxxXxxxXXXXxxxxXxXxXxxxXXXxxX
yyYYydyyasuyydgde32gyguygyugy3g4gug24ygy3g3ug4y42uy5g6yg4uyggryh
xxxxxxxxxxXXXXXXXXXXxxxxxxxxxXXxxXxXxxXxxxXXXXxxxxXxXxXxxxXXXxxX
yyYYydyyasuyydgde32gyguygyugy3g4gug24ygy3g3ug4y42uy5g6yg4uyggryh
xxxxxxxxxxXXXXXXXXXXxxxxxxxxxXXxxXxXxxXxxxXXXXxxxxXxXxXxxxXXXxxX
yyYYydyyasuyydgde32gyguygyugy3g4gug24ygy3g3ug4y42uy5g6yg4uyggryh
xxxxxxxxxxXXXXXXXXXXxxxxxxxxxXXxxXxXxxXxxxXXXXxxxxXxXxXxxxXXXxxX
yyYYydyyasuyydgde32gyguygyugy3g4gug24ygy3g3ug4y42uy5g6yg4uyggryh
xxxxxxxxxxXXXXXXXXXXxxxxxxxxxXXxxXxXxxXxxxXXXXxxxxXxXxXxxxXXXxxX
yyYYydyyasuyydgde32gyguygyugy3g4gug24ygy3g3ug4y42uy5g6yg4uyggryh
xxxxxxxxxxXXXXXXXXXXxxxxxxxxxXXxxXxXxxXxxxXXXXxxxxXxXxXxxxXXXxxX
yyYYydyyasuyydgde32gyguygyugy3g4gug24ygy3g3ug4y42uy5g6yg4uyggryh
xxxxxxxxxxXXXXXXXXXXxxxxxxxxxXXxxXxXxxXxxxXXXXxxxxXxXxXxxxXXXxxX
yyYYydyyasuyydgde32gyguygyugy3g4gug24ygy3g3ug4y42uy5g6yg4uyggryh
xxxxxxxxxxXXXXXXXXXXxxxxxxxxxXXxxXxXxxXxxxXXXXxxxxXxXxXxxxXXXxxX
yyYYydyyasuyydgde32gyguygyugy3g4gug24ygy3g3ug4y42uy5g6yg4uyggryh
xxxxxxxxxxXXXXXXXXXXxxxxxxxxxXXxxXxXxxXxxxXXXXxxxxXxXxXxxxXXXxxX
yyYYydyyasuyydgde32gyguygyugy3g4gug24ygy3g3ug4y42uy5g6yg4uyggryh
xxxxxxxxxxXXXXXXXXXXxxxxxxxxxXXxxXxXxxXxxxXXXXxxxxXxXxXxxxXXXxxX
yyYYydyyasuyydgde32gyguygyugy3g4gug24ygy3g3ug4y42uy5g6yg4uyggryh
xxxXXxxXxxxxXXxXXXXxxXXxXxxXXXXXXXXXXXXXxxxXXXXXXXXXxx==
-----END RSA PRIVATE KEY-----";


        [HttpPost]
        public async Task<IActionResult> Login([FromBody] LoginRequest req)
        {

            var uid = await ValidateJWT(req.id_token);

            var asyncJwt = CreateJWT();

            var httpClient = new HttpClient();

            httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", asyncJwt);
            var response = await httpClient.GetStringAsync($"https://accounts.us1.gigya.com/accounts.getAccountInfo?apikey={apikey}&uid={uid}");


            return Json(response);
        }

        public Task<IActionResult> Schema()
        {
            return getConfig("accounts.getSchema");
        }

        public Task<IActionResult> Policy()
        {
            return getConfig("accounts.getPolicies");
        }


        private async Task<IActionResult> getConfig(string api)
        {
            var datacenters = new string[] { "us1", "eu1", "au1" };

            var schemasResults = await Task.WhenAll(datacenters
                            .Select(x => new HttpRequestMessage(HttpMethod.Get, $"https://accounts.{x}-st2.gigya.com/{api}?apikey={apikey}"))
                            .Select(x =>
                            {
                                x.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", CreateJWT());
                                return x;
                            })
                            .Select(x => _httpClient.SendAsync(x))
                            .Select(async x => await (await x).Content.ReadAsStringAsync())
                            .Select(async x =>
                            {
                                var o = JObject.Parse(await x);
                                o.Remove("callId");
                                o.Remove("time");
                                return o;
                            }));

            var jdp = new JsonDiffPatch();
            var us1Schema = schemasResults.First();
            var schemas = schemasResults
                    .Skip(1)
                    .Select(x => jdp.Diff(us1Schema, x));

            return Json(schemas);
        }

        private string CreateJWT()
        {
            var rsaProvider = RsaUtils.DecodeRsaPrivateKey(key);

            var handler = new JwtSecurityTokenHandler();
            handler.SetDefaultTimesOnTokenCreation = false;
            handler.OutboundClaimTypeMap["jti"] = Guid.NewGuid().ToString();
            var rsakey = new RsaSecurityKey(rsaProvider);
            var descriptor = new SecurityTokenDescriptor
            {
                SigningCredentials = new SigningCredentials(rsakey, "RS256"),
                IssuedAt = DateTime.Now,
                Claims = new Dictionary<string, object>
                {
                    [JwtRegisteredClaimNames.Jti] = Guid.NewGuid().ToString()
                }
            };

            var token = handler.CreateJwtSecurityToken(descriptor);
            token.Payload.TryAdd("jti", Guid.NewGuid().ToString());
            token.Header["kid"] = "hdahjKJHu23u";


            var tokenString = handler.WriteToken(token);

            return tokenString;
        }

        private async Task<string> ValidateJWT(string idToken)
        {
            var httpClient = new HttpClient();

            var response = await httpClient.GetStringAsync("https://accounts.us1.gigya.com/accounts.getJWTPublicKey?apikey=<Gigya/CDC-API-Key>&v2=true");

            var jwks = new JsonWebKeySet(response);

            var parameters = new TokenValidationParameters
            {
                ValidateAudience = false,
                ValidateIssuer = true,
                ValidIssuer = "https://fidm.gigya.com/jwt/<Gigya/CDC-API-Key>",
                ValidateLifetime = true,
                IssuerSigningKeys = jwks.Keys
            };

            var handler = new JwtSecurityTokenHandler();

            handler.InboundClaimTypeMap.Clear();

            SecurityToken jwt;
            ClaimsPrincipal claimsPrincipal = handler.ValidateToken(idToken, parameters, out jwt);
            var uid = claimsPrincipal.FindFirst("sub").Value;
            return uid;
        }

        public IActionResult Index()
        {
            return View();
        }

        public IActionResult Global()
        {
            return View();
        }

        public IActionResult Replay()
        {
            return View();
        }

        public IActionResult About()
        {
            ViewData["Message"] = "Your application description page.";

            return View();
        }

        public IActionResult Contact()
        {
            ViewData["Message"] = "Your contact page.";

            return View();
        }

        public IActionResult Privacy()
        {
            return View();
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }

    }
}

Application and User Keys

Generate an application or user key in the SAP Customer Data Cloud Console, and use them to sign API requests. User keys are subject to the key's permissions and are logged for auditing purposes. 

Another benefit of using a userKey and secret is that the user does not have to construct or check signatures, as all requests are conducted over HTTPS.

When you pass a request across HTTPS, include the site's API key and the application key and secret (or user key and secret). For example:

 

https://...?apiKey=[SITE_API_KEY]&userKey=[APPLICATION_KEY]&secret=[APPLICATION_SECRET]

All calls should be made over HTTPS.

 

For more information about user keys, including instructions for finding your user key in the Console, see Using the User Key.

Instructional Video

If you have an SAP logon, you can watch an instructional video about managing applications here.

curl Code Example

curl https://accounts.us1.gigya.com/accounts.search 
 --data-urlencode "apiKey=3_mKxxxxXXXXXXXXxxxxxxxxXXXXxxXxxxxxxxxxxxxxxxxxxXXXXXXXXXXXXxxxxx" 
 --data-urlencode "userKey=AJxXXxXxxX2X" 
 --data-urlencode "secret=X73xXXXXXxxxxXXXxxxXXXx656767Xxx" 
 --data-urlencode "format=json" 
 --data-urlencode "query=select UID, identities.provider, identities.providerUID from accounts limit 10"

In the above example, the secret is the secret associated with the userKey, not the account secret located in the Console homepage.