Last active
February 19, 2018 13:52
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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