Skip to content

Instantly share code, notes, and snippets.

@guywald
Last active February 19, 2018 13:52
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save guywald/7a753e032ca2f979453b7f8aa4fb6569 to your computer and use it in GitHub Desktop.
Save guywald/7a753e032ca2f979453b7f8aa4fb6569 to your computer and use it in GitHub Desktop.
Unity 3d Facebook + AWS Cognito + AWS Api Gateway + AWS Lambda Authenticated Web Request
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using Facebook.Unity;
using System.Collections.Generic;
using UnityEngine.Experimental.Networking;
using Amazon;
using Amazon.Runtime;
using Amazon.CognitoIdentity;
using Amazon.CognitoIdentity.Model;
using Amazon.CognitoSync.SyncManager;
using System.Security.Cryptography;
using System.Text;
using System;
using System.Net;
using System.IO;
public class FeasibilityLite : MonoBehaviour
{
[Header("Button References")]
public Button loginButton;
public Button logoutButton;
/*
* Cognito Parameters
*/
[Header("Cognito Parameters")]
public string cognitoPool = "us-east-1:12345678-abcd-1234-ab12-abc123def456";
public RegionEndpoint cognitoRegion = RegionEndpoint.USEast1;
/*
* Request Parameters
*/
[Header("API Gateway Parametrs")]
public string host = "abcdefghij.execute-api.us-east-1.amazonaws.com";
public string canonicalUri = "/dev/testauthenticated";
public string apiKey = "PutApiKeyHereAndRememberToSetItInTheApiGateway";
public string apiGatewayRegion = RegionEndpoint.USEast1.SystemName;
void Awake ()
{
loginButton.onClick.AddListener (OnPressLogin);
logoutButton.onClick.AddListener (Logout);
}
// Use this for initialization
void Start ()
{
UnityInitializer.AttachToGameObject (this.gameObject);
}
/// <summary>
///Login to FB and start the process of authenticating the URL
///Steps:
///1) Initialize Facebook.
///2) Log-in and call the Cognito authentication with the token (If already logged in, just call Cognito auth).
/// </summary>
public void OnPressLogin ()
{
if (!FB.IsInitialized) {
FB.Init (FacebookInitCallback, OnHideUnity);
} else {
// Already initialized, signal an app activation App Event
if (FB.IsLoggedIn) {
FB.ActivateApp ();
CognitoLogin (Facebook.Unity.AccessToken.CurrentAccessToken.TokenString);
} else {
LoginFB ();
}
}
}
/// <summary>
/// Login to Facebook.
/// </summary>
private void LoginFB ()
{
// Signal an app activation App Event
var perms = new List<string> () { "public_profile", "email" };
FB.LogInWithReadPermissions (perms, FacebookLoginCallback);
FB.ActivateApp ();
}
/// <summary>
/// Callback from Facebook initialization.
/// Next step is to perform a facebook login.
/// </summary>
private void FacebookInitCallback ()
{
if (FB.IsInitialized)
LoginFB ();
else
Debug.LogError ("Failed to Initialize the Facebook SDK");
}
/// <summary>
/// Puase game when login screen appears
/// </summary>
/// <param name="isGameShown">If set to <c>true</c> is game shown.</param>
private void OnHideUnity (bool isGameShown)
{
if (!isGameShown)
Time.timeScale = 0; // Pause the game - we will need to hide
else
Time.timeScale = 1; // Resume the game - we're getting focus again
}
/// <summary>
/// Callback from Facebook login.
/// If succeeded, login to Cognit.
/// </summary>
/// <param name="result">Result.</param>
private void FacebookLoginCallback (ILoginResult result)
{
if (FB.IsLoggedIn) {
if (result.Error != null || !FB.IsLoggedIn)
Debug.LogError (result.Error);
else
CognitoLogin (result.AccessToken.TokenString);
} else
Debug.LogWarning ("User cancelled login");
}
/// <summary>
/// Login to Cognito with the Facebook token
/// </summary>
/// <param name="facebookToken">Facebook token.</param>
void CognitoLogin (string facebookToken)
{
CognitoAWSCredentials credentials = new CognitoAWSCredentials (cognitoPool,cognitoRegion);
credentials.AddLogin ("graph.facebook.com", facebookToken);
credentials.GetCredentialsAsync (CognitoGetCredentialsCallback, null);
}
private void CognitoGetCredentialsCallback (AmazonCognitoIdentityResult<ImmutableCredentials> result)
{
// if no exception, start the HTTP Get request
if (result.Exception == null)
StartCoroutine (AuthenticatedGet ((ImmutableCredentials)result.Response));
else
Debug.LogException (result.Exception);
}
IEnumerator AuthenticatedGet (ImmutableCredentials cognitoCredentials)
{
// ************* REQUEST VALUES *************
string accessKey = cognitoCredentials.AccessKey;
string secretKey = cognitoCredentials.SecretKey;
string securityToken = cognitoCredentials.Token;
string algorithm = "AWS4-HMAC-SHA256";
string method = "GET";
string service = "execute-api";
string serviceForSigning = "apigateway";
string contentType = "application/json";
string expires = "900";
string amzDate = DateTime.UtcNow.ToString ("yyyyMMddTHHmmssZ");
string dateStamp = DateTime.UtcNow.ToString ("yyyyMMdd");
// ************* TASK 1: CREATE A CANONICAL REQUEST *************
// **************************************************************
// Step 1: Define the verb (GET, POST, etc.)--already done.
// Step 2: Create canonical URI--the part of the URI from domain to query
/*
* Step 3: Create the canonical headers and signed headers. Header names
* and value must be trimmed and lowercase, and sorted in ASCII order.
* Note trailing \n in canonical_headers.
* signed_headers is the list of headers that are being included
* as part of the signing process. For requests that use query strings,
* only "host" is included in the signed headers.
*/
string payloadHash = HexEncode (Hash (ToBytes ("")));
SortedDictionary<string,string> headers = new SortedDictionary<string, string> {
{ "content-type",contentType },
{ "host",host },
{ "x-amz-content-sha256",payloadHash },
{ "x-amz-date",amzDate },
{ "x-amz-security-token", UriEncode (securityToken, true) },
};
string canonicalHeaders = string.Empty;
string signedHeaders = string.Empty;
foreach (var header in headers.Keys) {
canonicalHeaders += header.ToLowerInvariant () + ":" + headers [header].Trim () + "\n";
signedHeaders += header.ToLowerInvariant () + ";";
}
signedHeaders = signedHeaders.Substring (0, signedHeaders.Length - 1);
/*
* Match the algorithm to the hashing algorithm you use SHA-256 (recommended)
*/
string credentialScope = dateStamp + "/" + apiGatewayRegion + "/" + service + "/" + "aws4_request";
/*
* Step 4: Create the canonical query string. In this example, request
* parameters are in the query string. Query string values must
* be URL-encoded (space=%20). The parameters must be sorted by name.
*/
string canonicalQueryString = string.Empty;
canonicalQueryString += "X-Amz-Algorithm=" + UriEncode (algorithm, true);
canonicalQueryString += "&X-Amz-Content-Sha256=" + payloadHash;
canonicalQueryString += "&X-Amz-Credential=" + UriEncode (accessKey + "/" + credentialScope, true);
canonicalQueryString += "&X-Amz-Date=" + UriEncode (amzDate, true);
canonicalQueryString += "&X-Amz-Expires=" + expires;
canonicalQueryString += "&X-Amz-Security-Token=" + UriEncode (cognitoCredentials.Token, true);
canonicalQueryString += "&X-Amz-SignedHeaders=" + UriEncode (signedHeaders, true);
/*
* Step 5: Create payload hash. For GET requests, the payload is an
* empty string ("").
*/
// Step 6: Combine elements to create create canonical request
string canonicalRequest = method + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + payloadHash;
// ************* TASK 2: CREATE THE STRING TO SIGN*************
// ************************************************************
string stringToSign = algorithm + "\n" + amzDate + "\n" + credentialScope + "\n" + HexEncode (Hash (ToBytes (canonicalRequest)));
// ************* TASK 3: CALCULATE THE SIGNATURE *************
// ***********************************************************
//Create the signing key
byte[] signingKey = getSignatureKey (secretKey, dateStamp, apiGatewayRegion, serviceForSigning);
//Sign the string_to_sign using the signing_key
string signature = HexEncode (HmacSha256 (stringToSign, signingKey));
// ************* TASK 4: ADD SIGNING INFORMATION TO THE REQUEST *************
// **************************************************************************
/*
# The auth information can be either in a query string
# value or in a header named Authorization. This code shows how to put
# everything into a query string.
*/
canonicalQueryString += "&X-Amz-Signature=" + signature;
string requestUrl = "https://" + host + canonicalUri + '?' + canonicalQueryString;
UnityWebRequest wr = UnityWebRequest.Get (requestUrl);
wr.SetRequestHeader ("x-api-key", apiKey);
wr.SetRequestHeader ("content-type", "application/json");
wr.SetRequestHeader ("x-xmz-date", amzDate);
wr.SetRequestHeader ("x-amz-content-sha256", payloadHash);
wr.SetRequestHeader ("x-amz-security-token", UriEncode (cognitoCredentials.Token, true));
//************* SEND THE REQUEST *************
yield return wr.Send ();
//************ RESPONSE **********************
if (wr.isError) {
Debug.LogError (string.Format ("ERROR\ncode={0}\nmessage={1}\ntext={2}", wr.responseCode, wr.error, wr.downloadHandler.text));
} else {
/*
* Here is the response from AWS.
*/
Debug.Log ("Headers:\n\n");
foreach (var header in wr.GetResponseHeaders().Keys) {
Debug.Log (string.Format ("\n{0}:\t{1}", header, wr.GetResponseHeader (header)));
}
Debug.Log (string.Format ("\n\nBody:\n{0}", wr.downloadHandler.text));
}
}
#region Utilities
// Sign
static byte[] HmacSHA256 (String data, byte[] key)
{
String algorithm = "HmacSHA256";
KeyedHashAlgorithm kha = KeyedHashAlgorithm.Create (algorithm);
kha.Key = key;
return kha.ComputeHash (Encoding.UTF8.GetBytes (data));
}
// Create signing key
static byte[] getSignatureKey (String key, String dateStamp, String regionName, String serviceName)
{
byte[] kDate = HmacSha256 (dateStamp, ToBytes ("AWS4" + key));
byte[] kRegion = HmacSha256 (regionName, kDate);
byte[] kService = HmacSha256 (serviceName, kRegion);
return HmacSha256 ("aws4_request", kService);
}
public static String UriEncode (string strInput, bool encodeSlash)
{
char[] input = strInput.ToCharArray ();
StringBuilder result = new StringBuilder ();
for (int i = 0; i < input.Length; i++) {
char ch = input [i];
if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '-' || ch == '~' || ch == '.') {
result.Append (ch);
} else if (ch == '/') {
result.Append (encodeSlash ? "%2F" : ch.ToString ());
} else {
result.Append ("%" + Convert.ToByte (ch).ToString ("x2").ToUpper ());
}
}
return result.ToString ();
}
private static byte[] ToBytes (string str)
{
return Encoding.UTF8.GetBytes (str.ToCharArray ());
}
private static string HexEncode (byte[] bytes)
{
return BitConverter.ToString (bytes).Replace ("-", string.Empty).ToLowerInvariant ();
}
private static byte[] Hash (byte[] bytes)
{
return SHA256.Create ().ComputeHash (bytes);
}
private static byte[] HmacSha256 (string data, byte[] key)
{
return new HMACSHA256 (key).ComputeHash (ToBytes (data));
}
#endregion
/// <summary>
/// Logout Facebook.
/// </summary>
private void Logout ()
{
if (FB.IsLoggedIn)
FB.LogOut ();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment