/// <summary> /// TODO: single sign-on is not supported currently. /// Implements the logic for verifying and decoding authentication token issued by MSA. /// Note that authentication tokens are used for single sign-on only. /// </summary> /// <param name="userAuthenticationToken">Microsoft authentication token</param> /// <param name="appClientID">ClientID issued by Microsoft to the app that requested the token</param> /// <param name="appClientSecret">Client secret issued by Microsoft to the app that requested the token</param> /// <returns>Tuple: /// boolean: true if the authentication token was valid, false otherwise /// string: the user id in the authentication token. (TODO: return the JWT token instead). /// <c>OAuthException</c>: exception specific to <c>OAuth</c> encountered during token validation and decoding /// </returns> private Tuple <bool, string, OAuthException> VerifyAndDecodeMicrosoftAuthenticationToken(string userAuthenticationToken, string appClientID, string appClientSecret) { // Flag representing whether token has been verified bool tokenVerified = false; // Identity extracted from the token string tokenIdentity = null; // Exception raised upon verifying and decoding the token OAuthException tokenEx = null; // MSA authentication token simply gets decoded to a JWT JsonWebToken jwtToken = null; // Exception raised by LiveID decoding LiveAuthException liveEx = null; // Decode token. If cannot decode, create appropriate OAuthException. // DecodeAuthenticationToken does not appear to throw any errors. Instead, its errors are caught, and it returns false instead. if (LiveAuthWebUtility.DecodeAuthenticationToken(userAuthenticationToken, appClientSecret, out jwtToken, out liveEx) == false) { tokenEx = new OAuthException(OAuthErrors.ServiceUnavailable_503_Microsoft, liveEx /* pass out the LiveException also */); } else { //// TOKEN Validation checks if (jwtToken.IsExpired == true) { // Token expired. Handle this appropriately. tokenEx = new OAuthException(OAuthErrors.Unauthorized_401_1); } else if (appClientID != jwtToken.Claims.AppId) { // Token stolen by different app. Handle this appropriately. tokenEx = new OAuthException(OAuthErrors.Unauthorized_401_3); } else if (string.IsNullOrEmpty(jwtToken.Claims.UserId)) { // Token's id doesn't exist. Handle this appropriately. tokenEx = new OAuthException(OAuthErrors.Unauthorized_401_4); } else { // Extract the token's identity tokenIdentity = jwtToken.Claims.UserId; tokenVerified = true; } } // MSA's authentication token contains no information about a user other than it's account id. // Note that the account id found in an MSA authentication token is different than the actual account id. // Although couldn't find specs, it's likely this is due to privacy reasons. MSA issues a different account // id to different application publishers. In this ways, two publishers cannot track a single user accross // since the ids found in their auth tokens are different. However, this id is the same across two different // apps owned by the same publisher. return(new Tuple <bool, string, OAuthException>(tokenVerified, tokenIdentity, tokenEx)); }
/// <summary> /// Verifies a Facebook access token by asking for the corresponding debug token (Facebook's famous debug_token) from Facebook. /// This is better than checking an access token using implicit flow -- unlike implicit flow, this check ensures that the token /// was issued to an our app. (this means that the user typed his password in an window that listed our app name). Implicit flow /// only guarantees that a user typed his password in a window that listed some app name (not necessarily ours). /// </summary> /// <param name="userAccessToken">Facebook user access token</param> /// <param name="facebookAppID">AppID issued by Facebook to the app that requested the token</param> /// <param name="facebookAppSecret">App secret issued by Facebook to the app that requested the token</param> /// <returns>Tuple: /// boolean: true if the access token was valid, false otherwise /// <code>OAuthException</code>: exception specific to <c>OAuth</c> encountered during token validation and decoding /// </returns> private async Task <Tuple <bool, OAuthException> > FacebookDebugUserAccessToken(string userAccessToken, string facebookAppID, string facebookAppSecret) { // Flag representing whether token has been verified bool tokenVerified = false; // Exception raised upon verifying and decoding the token OAuthException tokenEx = null; try { // Using the nice Facebook library, we start by creating a FacebookClient with our app's credentials FacebookClient client = new FacebookClient(facebookAppID + "|" + facebookAppSecret); // Conver the token into a debugToken dynamic resultDebug = await client.GetTaskAsync <JsonObject>("debug_token", new { input_token = userAccessToken, }); // The result is a collection of JsonObjects. According to Facebook, there should never be more than one object // in the result of a debug_token if (resultDebug == null || resultDebug.Values == null || resultDebug.Values.Count != 1) { tokenEx = new OAuthException(OAuthErrors.ServiceUnavailable_503_Facebook); } else { // FB access token simply gets decoded into a debugToken FacebookDebugToken debugToken = JsonConvert.DeserializeObject <FacebookDebugToken>(Enumerable.First(resultDebug.Values).ToString()); // Token validation tests // Check whether the token is valid if (debugToken.IsValid == false) { // Token invalid. Handle this appropriately. tokenEx = new OAuthException(OAuthErrors.Unauthorized_401_2); } else if (facebookAppID != debugToken.AppId) { // Token stolen by different app. Handle this appropriately. tokenEx = new OAuthException(OAuthErrors.Unauthorized_401_3); } else if (OAuthUtil.BeginningOfTime.AddSeconds(debugToken.ExpiresAt).ToLocalTime() < DateTime.Now) { // Token expired. Handle this appropriately. tokenEx = new OAuthException(OAuthErrors.Unauthorized_401_1); } else if (string.IsNullOrEmpty(debugToken.UserId)) { // Token's id doesn't exist. Handle this appropriately. tokenEx = new OAuthException(OAuthErrors.Unauthorized_401_4); } else { // Token is verified. Yay! tokenVerified = true; } } } catch (Exception ex) { tokenEx = new OAuthException(OAuthErrors.ServiceUnavailable_503_Facebook, ex /* pass out the FacebookOOAuthException also */); } return(new Tuple <bool, OAuthException>(tokenVerified, tokenEx)); }