Did Facebook change their App developers API on March 2017? - facebook

Well, I am a new guy who is trying to develop a web application whose user are will be authenticated through Facebook. I am developing the application in MVC 4 .Net Framework. As it's internal programs are already done so I need not to do much coding. I have just put the API ID and Secret Key in the scope
OAuthWebSecurity.RegisterFacebookClient(
appId: "750397051803327",
appSecret: "**************************");
And here is my Application http://imgur.com/a/k4Vd0
My Application is taking properly the user permission from the user perfectly. http://imgur.com/a/bqzj5 but after taking permission it is not providing the login state of the user by showing such exception http://imgur.com/a/h81Oh login failed. I debugged form the code end and I observed that it is sending isLoggedin as false http://imgur.com/a/UuLIe therefore my I am not getting the access.
However 2 days before I am not getting such exception. I was getting data simply fine. Here is a snapshot of my previous data. http://imgur.com/a/Bc49F
I need that data again, but how? Is there anything need to change in my application dashboard? Possibly I have changed something in application dashboard. if yes then what is particularly that?
Another things I'm confused that what is the need for PRODUCTS? Do I need anything from the products for this special reason to get the data. If yes then which one shall I need and how to configure it to get back my previous systematic process in which I was getting data smoothly.
If I add App Center from the PRODUCTS I am obtaining two other secret keys like Account Kit App Secret and Account Kit Client Token Is that I need to use these keys for my requested case. For such login approval specific which Products are need or nothing need at all from PRODUCTS. I am so confused about it how to configure an application.
Please suggest me how to solve this problem in addition how to configure my application API. Thank you.

According to Previous Answer I got my solution. In MVC4 everyone write down their AppID and SecurityCode. Due to change of facebook GRAPH API those previous links are broken. Consequently everyone need to change the RegisterFacebookClient calss. But this class is a sealed class in the .Net library, so anyone can't extend or overwrite it. As a result we need to use a wrapper class. Let us consider my Wrapper class is FacebookClientV2Dot3 therefore my class will be
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using DotNetOpenAuth.AspNet.Clients;
using Newtonsoft.Json;
public class FacebookClientV2Dot3 : OAuth2Client
{
#region Constants and Fields
/// <summary>
/// The authorization endpoint.
/// </summary>
private const string AuthorizationEndpoint = "https://www.facebook.com/dialog/oauth";
/// <summary>
/// The token endpoint.
/// </summary>
private const string TokenEndpoint = "https://graph.facebook.com/oauth/access_token";
/// <summary>
/// The user info endpoint.
/// </summary>
private const string UserInfoEndpoint = "https://graph.facebook.com/me";
/// <summary>
/// The app id.
/// </summary>
private readonly string _appId;
/// <summary>
/// The app secret.
/// </summary>
private readonly string _appSecret;
/// <summary>
/// The requested scopes.
/// </summary>
private readonly string[] _requestedScopes;
#endregion
/// <summary>
/// Creates a new Facebook OAuth2 client, requesting the default "email" scope.
/// </summary>
/// <param name="appId">The Facebook App Id</param>
/// <param name="appSecret">The Facebook App Secret</param>
public FacebookClient(string appId, string appSecret)
: this(appId, appSecret, new[] { "email" }) { }
/// <summary>
/// Creates a new Facebook OAuth2 client.
/// </summary>
/// <param name="appId">The Facebook App Id</param>
/// <param name="appSecret">The Facebook App Secret</param>
/// <param name="requestedScopes">One or more requested scopes, passed without the base URI.</param>
public FacebookClient(string appId, string appSecret, params string[] requestedScopes)
: base("facebook")
{
if (string.IsNullOrWhiteSpace(appId))
throw new ArgumentNullException("appId");
if (string.IsNullOrWhiteSpace(appSecret))
throw new ArgumentNullException("appSecret");
if (requestedScopes == null)
throw new ArgumentNullException("requestedScopes");
if (requestedScopes.Length == 0)
throw new ArgumentException("One or more scopes must be requested.", "requestedScopes");
_appId = appId;
_appSecret = appSecret;
_requestedScopes = requestedScopes;
}
protected override Uri GetServiceLoginUrl(Uri returnUrl)
{
var state = string.IsNullOrEmpty(returnUrl.Query) ? string.Empty : returnUrl.Query.Substring(1);
return BuildUri(AuthorizationEndpoint, new NameValueCollection
{
{ "client_id", _appId },
{ "scope", string.Join(" ", _requestedScopes) },
{ "redirect_uri", returnUrl.GetLeftPart(UriPartial.Path) },
{ "state", state },
});
}
protected override IDictionary<string, string> GetUserData(string accessToken)
{
var uri = BuildUri(UserInfoEndpoint, new NameValueCollection { { "access_token", accessToken } });
var webRequest = (HttpWebRequest)WebRequest.Create(uri);
using (var webResponse = webRequest.GetResponse())
using (var stream = webResponse.GetResponseStream())
{
if (stream == null)
return null;
using (var textReader = new StreamReader(stream))
{
var json = textReader.ReadToEnd();
var extraData = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
var data = extraData.ToDictionary(x => x.Key, x => x.Value.ToString());
data.Add("picture", string.Format("https://graph.facebook.com/{0}/picture", data["id"]));
return data;
}
}
}
protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
{
var uri = BuildUri(TokenEndpoint, new NameValueCollection
{
{ "code", authorizationCode },
{ "client_id", _appId },
{ "client_secret", _appSecret },
{ "redirect_uri", returnUrl.GetLeftPart(UriPartial.Path) },
});
var webRequest = (HttpWebRequest)WebRequest.Create(uri);
string accessToken = null;
HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse();
// handle response from FB
// this will not be a url with params like the first request to get the 'code'
Encoding rEncoding = Encoding.GetEncoding(response.CharacterSet);
using (StreamReader sr = new StreamReader(response.GetResponseStream(), rEncoding))
{
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
var jsonObject = serializer.DeserializeObject(sr.ReadToEnd());
var jConvert = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(jsonObject));
Dictionary<string, object> desirializedJsonObject = JsonConvert.DeserializeObject<Dictionary<string, object>>(jConvert.ToString());
accessToken = desirializedJsonObject["access_token"].ToString();
}
return accessToken;
}
private static Uri BuildUri(string baseUri, NameValueCollection queryParameters)
{
var keyValuePairs = queryParameters.AllKeys.Select(k => HttpUtility.UrlEncode(k) + "=" + HttpUtility.UrlEncode(queryParameters[k]));
var qs = String.Join("&", keyValuePairs);
var builder = new UriBuilder(baseUri) { Query = qs };
return builder.Uri;
}
/// <summary>
/// Facebook works best when return data be packed into a "state" parameter.
/// This should be called before verifying the request, so that the url is rewritten to support this.
/// </summary>
public static void RewriteRequest()
{
var ctx = HttpContext.Current;
var stateString = HttpUtility.UrlDecode(ctx.Request.QueryString["state"]);
if (stateString == null || !stateString.Contains("__provider__=facebook"))
return;
var q = HttpUtility.ParseQueryString(stateString);
q.Add(ctx.Request.QueryString);
q.Remove("state");
ctx.RewritePath(ctx.Request.Path + "?" + q);
}
}
Look here you I have replaces all the API links by newer version links.
Now you need to modify your
AuthConfig
Just use a wrapper class
OAuthWebSecurity.RegisterClient(new FacebookClientV2Dot3("AppID", "HassedPassword"));
Then all success. You facebook login will be back in previous state.
However you can face a new issue regarding this new API rather than previous API, the problem is that IP Whitelisting. Like this image. Hope you will need nothing but this. Happy coding.

Yes!!!!
I got the solution of my own problem. According to Facebook developers bug report all Facebook log is not working from 28th March 2017. They also let us know through their developers Facebook group. The post link is here.
According to one of the Developer teams had said we're finding that
facebook authentication just stopped working (2pm EST) across multiple
apps that we manage. apps haven't changed, apps haven't been
suspended..... not sure where to report this since "status" is all
good......

Related

Return a custom response when using the Authorize Attribute on a controller

I have just implemented the Bearer token and I have added the Authorize attribute to my controller class and that works fine. It looks like this:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
What I would like to do is to create a more complex response from the server when it fails, rather then the standard 401.
I tried filters but they are not invoked at all.
Any ideas how to do this?
Have a custom scheme, custom authorization handler and poof!
Notice that I injected the Handler in ConfigureServices:
services.AddAuthentication(options =>
{
options.DefaultScheme = ApiKeyAuthenticationOptions.DefaultScheme;
options.DefaultAuthenticateScheme = ApiKeyAuthenticationOptions.DefaultScheme;
})
.AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>(
ApiKeyAuthenticationOptions.DefaultScheme, o => { });
ApiKeyAuthenticationOptions
public class ApiKeyAuthenticationOptions : AuthenticationSchemeOptions
{
public const string DefaultScheme = "API Key";
public string Scheme => DefaultScheme;
public string AuthenticationType = DefaultScheme;
public const string HeaderKey = "X-Api-Key";
}
ApiKeyAuthenticationHandler
/// <summary>
/// An Auth handler to handle authentication for a .NET Core project via Api keys.
///
/// This helps to resolve dependency issues when utilises a non-conventional method.
/// https://stackoverflow.com/questions/47324129/no-authenticationscheme-was-specified-and-there-was-no-defaultchallengescheme-f
/// </summary>
public class ApiKeyAuthenticationHandler : AuthenticationHandler<ApiKeyAuthenticationOptions>
{
private readonly IServiceProvider _serviceProvider;
public ApiKeyAuthenticationHandler(IOptionsMonitor<ApiKeyAuthenticationOptions> options, ILoggerFactory logger,
UrlEncoder encoder, ISystemClock clock, IServiceProvider serviceProvider)
: base (options, logger, encoder, clock)
{
_serviceProvider = serviceProvider;
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var token = Request.Headers[ApiKeyAuthenticationOptions.HeaderKey];
if (string.IsNullOrEmpty(token)) {
return Task.FromResult (AuthenticateResult.Fail ("Token is null"));
}
var customRedisEvent = _serviceProvider.GetRequiredService<ICustomRedisEvent>();
var isValidToken = customRedisEvent.Exists(token, RedisDatabases.ApiKeyUser);
if (!isValidToken) {
return Task.FromResult (AuthenticateResult.Fail ($"Invalid token {token}."));
}
var claims = new [] { new Claim ("token", token) };
var identity = new ClaimsIdentity (claims, nameof (ApiKeyAuthenticationHandler));
var ticket = new AuthenticationTicket (new ClaimsPrincipal (identity), Scheme.Name);
return Task.FromResult (AuthenticateResult.Success (ticket));
}
}
Focus on the handler class. Apart from the sample code I've provided, simply utilise the base class properties like Response to set your custom http status code or whatever you may need!
Here's the derived class if you need it.
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.authenticationhandler-1?view=aspnetcore-3.1

How to authenticate with Azure AD / OpenId but use Entity Framework based user/role data

I'm trying to improve the authentication story for a legacy ASPNet MVC/OWIN app - Currently, it uses the AspNetUsers / AspNetRoles / claims etc tables along with forms + cookie based authentication.
I want to use Azure AD / OpenID Connect for authentication but then load the user profile/roles from the database as currently. Basically, no more password management within the app. Users themselves will still need to exist/be created within the app.
The application is quite dependent on some custom data associated with these users so simply using the roles from Active Directory isn't an option.
The OpenID auth works, however I'm not sure how to use the existing Identityuser / IdentityUserRole / RoleManager plumbing in conjunction with it.
Basically once the user authenticates with Open ID we'll want to load the corresponding user from the database (matching on email address) and use that user profile / roles going forward.
In particular, the AuthorizeAttribute (with specific roles specified) should continue to function as before.
This is what I have so far:
public class IdentityConfig
{
public void Configuration(IAppBuilder app)
{
app.CreatePerOwinContext(AppIdentityDbContext.Create);
app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);
app.CreatePerOwinContext<AppRoleManager>(AppRoleManager.Create);
ConfigureAuth(app);
}
/// <summary>
/// Configures OpenIDConnect Authentication & Adds Custom Application Authorization Logic on User Login.
/// </summary>
/// <param name="app">The application represented by a <see cref="IAppBuilder"/> object.</param>
private void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
//Configure OpenIDConnect, register callbacks for OpenIDConnect Notifications
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = ConfigHelper.ClientId,
Authority = String.Format(CultureInfo.InvariantCulture, ConfigHelper.AadInstance,
ConfigHelper.Tenant), // For Single-Tenant
PostLogoutRedirectUri = ConfigHelper.PostLogoutRedirectUri,
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
RoleClaimType = "roles",
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error/OtherError?errorDescription=" +
context.Exception.Message);
return Task.FromResult(0);
},
SecurityTokenValidated = async context =>
{
string userIdentityName = context.AuthenticationTicket.Identity.Name;
var userManager = context.OwinContext.GetUserManager<AppUserManager>();
var user = userManager.FindByEmail(userIdentityName);
if (user == null)
{
Log.Error("User {name} authenticated with open ID, but unable to find matching user in store", userIdentityName);
context.HandleResponse();
context.Response.Redirect("/Error/NoAccess?identity=" + userIdentityName);
return;
}
user.DateLastLogin = DateTime.Now;
IdentityResult result = await userManager.UpdateAsync(user);
if (result.Succeeded)
{
var authManager = context.OwinContext.Authentication;
ClaimsIdentity ident = await userManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ExternalBearer);
// Attach additional claims from DB user
authManager.User.AddIdentity(ident);
// authManager.SignOut();
// authManager.SignIn(new AuthenticationProperties { IsPersistent = false }, ident);
return;
}
throw new Exception(string.Format("Failed to update user {0} after log-in", userIdentityName));
}
}
});
}
}
Here's what I ended up doing:
public class IdentityConfig
{
public void Configuration(IAppBuilder app)
{
app.CreatePerOwinContext(AppIdentityDbContext.Create);
app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);
app.CreatePerOwinContext<AppRoleManager>(AppRoleManager.Create);
ConfigureAuth(app);
}
/// <summary>
/// Configures OpenIDConnect Authentication & Adds Custom Application Authorization Logic on User Login.
/// </summary>
/// <param name="app">The application represented by a <see cref="IAppBuilder"/> object.</param>
private void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieDomain = ConfigHelper.AuthCookieDomain,
SlidingExpiration = true,
ExpireTimeSpan = TimeSpan.FromHours(2)
});
//Configure OpenIDConnect, register callbacks for OpenIDConnect Notifications
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = ConfigHelper.ClientId,
Authority = String.Format(CultureInfo.InvariantCulture, ConfigHelper.AadInstance, ConfigHelper.Tenant),
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
RoleClaimType = ClaimTypes.Role
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error/OtherError?errorDescription=" + context.Exception.Message);
return Task.FromResult(0);
},
RedirectToIdentityProvider = context =>
{
// Set the post-logout & redirect URI dynamically depending on the incoming request.
// That allows us to use the same Azure AD app for two subdomains (these two domains give different app behaviour)
var builder = new UriBuilder(context.Request.Uri);
builder.Fragment = builder.Path = builder.Query = "";
context.ProtocolMessage.PostLogoutRedirectUri = builder.ToString();
context.ProtocolMessage.RedirectUri = builder.ToString();
return Task.FromResult(0);
}
}
});
app.Use<EnrichIdentityWithAppUserClaims>();
}
}
public class EnrichIdentityWithAppUserClaims : OwinMiddleware
{
public EnrichIdentityWithAppUserClaims(OwinMiddleware next) : base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
await MaybeEnrichIdentity(context);
await Next.Invoke(context);
}
private async Task MaybeEnrichIdentity(IOwinContext context)
{
ClaimsIdentity openIdUserIdentity = (ClaimsIdentity)context.Authentication.User.Identity;
string userIdentityName = openIdUserIdentity.Name;
var userManager = context.GetUserManager<AppUserManager>();
var appUser = userManager.FindByEmail(userIdentityName);
if (appUser == null)
{
Log.Error("User {name} authenticated with open ID, but unable to find matching user in store", userIdentityName);
return;
}
appUser.DateLastLogin = DateTime.Now;
IdentityResult result = await userManager.UpdateAsync(appUser);
if (result.Succeeded)
{
ClaimsIdentity appUserIdentity = await userManager.CreateIdentityAsync(appUser, DefaultAuthenticationTypes.ExternalBearer);
openIdUserIdentity.AddClaims(appUserIdentity.Claims);
}
}
}
It's quite similar to what I had originally - (note: RoleClaimType = ClaimTypesRoles not "roles") except instead of trying to deal with the user in the SecurityTokenValidated callback, I've added some custom middleware that finds a matching user (by email address) and adds the claims (app roles) from the matching app user to the authenticated user identity (OpenID identity).
Finally, I protected all controller actions with a (custom) AuthorizeAttribute (not shown here) that ensures the authenticated user at least belongs to the "User" role (if not, redirects them to a "no access" page indicating that we've authenticated them but they have no access in the system).
The OpenID auth works, however I'm not sure how to use the existing Identityuser / IdentityUserRole / RoleManager plumbing in conjunction with it.
The application is quite dependent on some custom data associated with these users so simply using the roles from Active Directory isn't an option.
For your requirement, I assume that you could build your identity server (e.g. IdentityServer3) and leverage IdentityServer3.AspNetIdentity for identity management using ASP.NET Identity.
For your web client application, you could use OpenID Connect middleware and set Authority to your custom identity server and set your pre-configured ClientId on your identity server.
Moreover, you could follow this tutorial for quickly getting started with IdentityServer3 and the full samples IdentityServer3 Samples.

Connect Microsoft QnA Maker to Bot Framework

I have created a bot using QnA Maker and set up a FB App connected to our FB page and Microsoft Bot Framework. However, something is missing. How do I connect Microsoft QnA maker to Bot Framework? (FWIW - the goal is a FB Messenger bot that answers FAQ regarding a non profit event). Thanks
You cannot directly link the QnAMaker endpoint to FB. You first need to create a Bot Service using the QnAMaker template and then enable it on FB channel. See https://learn.microsoft.com/en-us/bot-framework/azure/azure-bot-service-quickstart
You nede to register on QNA maker and use the below code to get the response. no need to register on bot framework.
Sample Request
string responseString = string.Empty;
var query = “hi”; //User Query
var knowledgebaseId = “YOUR_KNOWLEDGE_BASE_ID”; // Use knowledge base id created.
var qnamakerSubscriptionKey = “YOUR_SUBSCRIPTION_KEY”; //Use subscription key assigned to you.
//Build the URI
Uri qnamakerUriBase = new Uri("https://westus.api.cognitive.microsoft.com/qnamaker/v1.0");
var builder = new UriBuilder($"{qnamakerUriBase}/knowledgebases/{knowledgebaseId}/generateAnswer");
//Add the question as part of the body
var postBody = $"{{\"question\": \"{query}\"}}";
//Send the POST request
using (WebClient client = new WebClient())
{
//Set the encoding to UTF8
client.Encoding = System.Text.Encoding.UTF8;
//Add the subscription key header
client.Headers.Add("Ocp-Apim-Subscription-Key", qnamakerSubscriptionKey);
client.Headers.Add("Content-Type", "application/json");
responseString = client.UploadString(builder.Uri, postBody);
}
Sample Response
using Newtonsoft.Json;
private class QnAMakerResult
{
/// <summary>
/// The top answer found in the QnA Service.
/// </summary>
[JsonProperty(PropertyName = "answer")]
public string Answer { get; set; }
/// <summary>
/// The score in range [0, 100] corresponding to the top answer found in the QnA Service.
/// </summary>
[JsonProperty(PropertyName = "score")]
public double Score { get; set; }
}
//De-serialize the response
QnAMakerResult response;
try
{
response = JsonConvert.DeserializeObject< QnAMakerResult >(responseString);
}
catch
{
throw new Exception("Unable to deserialize QnA Maker response string.");
}
Note: to get knowledge base id and subscription key you need to login and create a service
do let me know in case you need any help
In order to use QnAMaker in bot framework you may handle t when you get a none intent or some intent.
Simply add this in your LUIS dialog
[LuisModel("modelID", "SubscriptionKey")]
[Serializable]
public class RootDialog : LuisDialog<object>
{
[LuisIntent("None")]
public async Task NoneRes(IDialogContext context, LuisResult result)
{
var qnadialog = new QnADialog();
await context.Forward(new QnADialog(), AfterQnADialog, context.Activity, CancellationToken.None);
}
private async Task AfterQnADialog(IDialogContext context, IAwaitable<object> result)
{
var answerFound = await result;
// handle after qna response
}
}
And add this in your QnADialog
[Serializable]
[QnAMaker(authKey: "AuthKey", knowledgebaseId: "KnowledgebaseId", defaultMessage: "please rephrase, I could not understand.", scoreThreshold: 0.5, top: 1, endpointHostName: "https://yourAccount.azurewebsites.net/qnamaker")]
public class QnADialog : QnAMakerDialog
{}
Done.
Hope this will help

Windows phone 8 and Unity 5 support for Facebook SDK

This is a request for support for WP8 and Unity 5 for the Facebook SDK.
I am a developer wanting to produce games across many platforms, and being able to publish to the Windows Phone 8 store is a big part of my agenda.
Is support for this platform scheduled to be released any time soon? If so, when are you looking to release it?
Also, is there an imminent release for the Unity SDK that is designed to work with Unity 5? I have managed to get the current release to work, but as it is not yet fully supported, I do not know what will work and what won't.
Any information regarding these issues would be much appreciated!
Update: I ran into this video that explains how the "AndContinue" methods are going away in windows 10 so we can use one single method; the Async methods that already exists on windows 8/8.1 api. Check it out at https://youtu.be/aFVAP3fNJVo?t=23m34s
I was in the same place as you but managed to get it working. When I have time, ill probably write a blog on my experiences.
Here is some of the main code that directly calls on WebAuthenticationBroker:-
static bool isTryingToRegister { get; set; }
private static string _FbToken;
public static string response;
static bool isLoggedIn { get; set; }
static private string AppID { get { return "000000000000000000"; } }
static private Uri callback { get; set; }
static private string permissions { get { return "public_profile, user_friends, email, publish_actions, user_photos"; } }
public static System.Action<object> AuthSuccessCallback;
public static System.Action<object> AuthFailedCallback;
public static string fbToken
{
get
{
return _FbToken;
}
}
static Uri loginUrl
{
get
{
return new Uri(String.Format("https://www.facebook.com/v2.0/dialog/oauth/?client_id={0}&display=popup&response_type=token&redirect_uri={1}&scope={2}",
AppID,
callback,
permissions));
}
}
public static IEnumerator _Run_ConnectWithFacebook()
{
yield return new WaitForEndOfFrame();
#if UNITY_EDITOR
UnityDebugAuthentication();
#endif
#if NETFX_CORE
WindowsStoreAuthenticate();
#endif
}
#if NETFX_CORE
static void WindowsStoreAuthenticate()
{
#if UNITY_WP_8_1 && !UNITY_EDITOR
AuthSuccessCallback = _Run_ConnectWithFacebook_SuccessResponse;
AuthFailedCallback = _Run_ConnectWithFacebook_FailedResponse;
UnityEngine.WSA.Application.InvokeOnUIThread(
() =>
{
callback = WebAuthenticationBroker.GetCurrentApplicationCallbackUri();
WebAuthenticationBroker.AuthenticateAndContinue(loginUrl, callback, null, WebAuthenticationOptions.None);
}, true);
#endif
#if UNITY_METRO_8_1 && !UNITY_EDITOR
AuthSuccessCallback = _Run_ConnectWithFacebook_SuccessResponse;
AuthFailedCallback = _Run_ConnectWithFacebook_FailedResponse;
UnityEngine.WSA.Application.InvokeOnUIThread(
async () =>
{
callback = WebAuthenticationBroker.GetCurrentApplicationCallbackUri();
WebAuthenticationResult authResult = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, loginUrl);
}, true);
#endif
}
#endif
Note how I use WebAuthenticationBroker.AuthenticateAndContinue for windows phone and WebAuthenticationBroker.AuthenticateAsync for windows 8. This is necessary for their respective platforms. You may want to look into getting the WebAuthenticationBroker which is a Microsoft class that works on WP8 and WSA (im guessing you are aiming for WSA, so am I):-
https://msdn.microsoft.com/library/windows/apps/windows.security.authentication.web.webauthenticationbroker.aspx?f=255&MSPPError=-2147217396
You will also need to build a c# project in unity so you can implement part 2 of it which includes handling when the token details are returned to the app. Here is my code sample of App.xaml.cs which was inspired by https://msdn.microsoft.com/en-us/library/dn631755.aspx:-
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Core;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using UnityPlayer;
using Template.Common;
using Windows.Security.Authentication.Web;
// The Blank Application template is documented at http://go.microsoft.com/fwlink/?LinkId=234227
namespace Template
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
sealed partial class App : Application
{
private WinRTBridge.WinRTBridge _bridge;
private AppCallbacks appCallbacks;
#if UNITY_WP_8_1
public ContinuationManager continuationManager { get; private set; }
#endif
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
this.InitializeComponent();
appCallbacks = new AppCallbacks();
appCallbacks.RenderingStarted += RemoveSplashScreen;
#if UNITY_WP_8_1
this.Suspending += OnSuspending;
continuationManager = new ContinuationManager();
#endif
}
/// <summary>
/// Invoked when application is launched through protocol.
/// Read more - http://msdn.microsoft.com/library/windows/apps/br224742
/// </summary>
/// <param name="args"></param>
protected override void OnActivated(IActivatedEventArgs args)
{
#if UNITY_WP_8_1
var continuationEventArgs = args as IContinuationActivatedEventArgs;
if (continuationEventArgs != null)
{
ContinueWebAuthentication(args as WebAuthenticationBrokerContinuationEventArgs);
return;
//}
}
#endif
string appArgs = "";
Windows.ApplicationModel.Activation.SplashScreen splashScreen = null;
switch (args.Kind)
{
case ActivationKind.Protocol:
ProtocolActivatedEventArgs eventArgs = args as ProtocolActivatedEventArgs;
splashScreen = eventArgs.SplashScreen;
appArgs += string.Format("Uri={0}", eventArgs.Uri.AbsoluteUri);
break;
}
InitializeUnity(appArgs, splashScreen);
}
#if UNITY_WP_8_1
public void ContinueWebAuthentication(WebAuthenticationBrokerContinuationEventArgs args)
{
WebAuthenticationResult result = args.WebAuthenticationResult;
if (result.ResponseStatus == WebAuthenticationStatus.Success)
{
string responseData = result.ResponseData.Substring(result.ResponseData.IndexOf("access_token"));
String[] keyValPairs = responseData.Split('&');
string access_token = null;
string expires_in = null;
for (int i = 0; i < keyValPairs.Length; i++)
{
String[] splits = keyValPairs[i].Split('=');
switch (splits[0])
{
case "access_token":
access_token = splits[1]; //you may want to store access_token for further use. Look at Scenario5 (Account Management).
break;
case "expires_in":
expires_in = splits[1];
break;
}
}
AppCallbacks.Instance.UnityActivate(Window.Current.CoreWindow, CoreWindowActivationState.CodeActivated);
AppCallbacks.Instance.InvokeOnAppThread(() =>
{
// back to Unity
//function to call or variable that accepts the access token
}, false);
//OutputToken(result.ResponseData.ToString());
//await GetFacebookUserNameAsync(result.ResponseData.ToString());
}
else if (result.ResponseStatus == WebAuthenticationStatus.ErrorHttp)
{
//OutputToken("HTTP Error returned by AuthenticateAsync() : " + result.ResponseErrorDetail.ToString());
AppCallbacks.Instance.InvokeOnAppThread(() =>
{
// back to Unity
//function to call indicating something went wrong
}, false);
}
else if(result.ResponseStatus == WebAuthenticationStatus.UserCancel)
{
//OutputToken("Error returned by AuthenticateAsync() : " + result.ResponseStatus.ToString());
AppCallbacks.Instance.InvokeOnAppThread(() =>
{
// back to Unity
//function to call indicating something went wrong
}, false);
}
}
#endif
/// <summary>
/// Invoked when application is launched via file
/// Read more - http://msdn.microsoft.com/library/windows/apps/br224742
/// </summary>
/// <param name="args"></param>
protected override void OnFileActivated(FileActivatedEventArgs args)
{
string appArgs = "";
Windows.ApplicationModel.Activation.SplashScreen splashScreen = null;
splashScreen = args.SplashScreen;
appArgs += "File=";
bool firstFileAdded = false;
foreach (var file in args.Files)
{
if (firstFileAdded) appArgs += ";";
appArgs += file.Path;
firstFileAdded = true;
}
InitializeUnity(appArgs, splashScreen);
}
/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used when the application is launched to open a specific file, to display
/// search results, and so forth.
/// </summary>
/// <param name="args">Details about the launch request and process.</param>
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
InitializeUnity(args.Arguments, args.SplashScreen);
}
private void InitializeUnity(string args, Windows.ApplicationModel.Activation.SplashScreen splashScreen)
{
#if UNITY_WP_8_1
ApplicationView.GetForCurrentView().SuppressSystemOverlays = true;
#pragma warning disable 4014
StatusBar.GetForCurrentView().HideAsync();
#pragma warning restore 4014
#endif
appCallbacks.SetAppArguments(args);
Frame rootFrame = Window.Current.Content as Frame;
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (rootFrame == null && !appCallbacks.IsInitialized())
{
var mainPage = new MainPage(splashScreen);
Window.Current.Content = mainPage;
Window.Current.Activate();
// Setup scripting bridge
_bridge = new WinRTBridge.WinRTBridge();
appCallbacks.SetBridge(_bridge);
#if !UNITY_WP_8_1
appCallbacks.SetKeyboardTriggerControl(mainPage);
#endif
appCallbacks.SetSwapChainPanel(mainPage.GetSwapChainPanel());
appCallbacks.SetCoreWindowEvents(Window.Current.CoreWindow);
appCallbacks.InitializeD3DXAML();
}
Window.Current.Activate();
#if UNITY_WP_8_1
SetupLocationService();
#endif
}
private void RemoveSplashScreen()
{
// This will fail if you change main window class
// Make sure to adjust accordingly if you do something like this
MainPage page = (MainPage)Window.Current.Content;
page.RemoveSplashScreen();
}
#if UNITY_WP_8_1
// This is the default setup to show location consent message box to the user
// You can customize it to your needs, but do not remove it completely if your application
// uses location services, as it is a requirement in Windows Store certification process
private async void SetupLocationService()
{
if (!appCallbacks.IsLocationCapabilitySet())
{
return;
}
const string settingName = "LocationContent";
bool userGaveConsent = false;
object consent;
var settings = Windows.Storage.ApplicationData.Current.LocalSettings;
var userWasAskedBefore = settings.Values.TryGetValue(settingName, out consent);
if (!userWasAskedBefore)
{
var messageDialog = new Windows.UI.Popups.MessageDialog("Can this application use your location?", "Location services");
var acceptCommand = new Windows.UI.Popups.UICommand("Yes");
var declineCommand = new Windows.UI.Popups.UICommand("No");
messageDialog.Commands.Add(acceptCommand);
messageDialog.Commands.Add(declineCommand);
userGaveConsent = (await messageDialog.ShowAsync()) == acceptCommand;
settings.Values.Add(settingName, userGaveConsent);
}
else
{
userGaveConsent = (bool)consent;
}
if (userGaveConsent)
{ // Must be called from UI thread
appCallbacks.SetupGeolocator();
}
}
#endif
/// <summary>
/// Invoked when Navigation to a certain page fails
/// </summary>
/// <param name="sender">The Frame which failed navigation</param>
/// <param name="e">Details about the navigation failure</param>
void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
}
/// <summary>
/// Invoked when application execution is being suspended. Application state is saved
/// without knowing whether the application will be terminated or resumed with the contents
/// of memory still intact.
/// </summary>
/// <param name="sender">The source of the suspend request.</param>
/// <param name="e">Details about the suspend request.</param>
private async void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
await SuspensionManager.SaveAsync();
deferral.Complete();
}
}
}
You will also need the ContinuationManager.cs class and the SuspensionManager.cs class but mainly for WP8.1 Universal. This isnt needed for W8.1 and so you should ensure that you are using #defines to keep them within their own contexts
If you want, instead of calling on WebAuthenticationBroker, you could call on the facebook app directly but I don't know of all the details. You can read up on that on facebook's website developers.facebook.com/docs/facebook-login/login-for-windows-phone. That method isnt recommended incase the user doesn't have it installed.
Long (rushed) story short, call WebAuthenticationBroker, handle the continuation Event in OnActivated event to catch the WebAuthenticationBrokerContinuationEventArgs object coming through and use the data however you see fit. Be sure to use the following if you want to call any code in the unity side. Note that you can directly access the c# code from App.xaml.cs:-
AppCallbacks.Instance.InvokeOnAppThread(() =>
{
// back to Unity
//your code here
}, false);
Also note that this is mainly to get the access token. Once we have that, we can make basic WWW calls directly to facebook and get data back from it. The data will be returned as a JSON format (ps this is such an awesome clean format!) you can use the .NET json library to serialize it into a class. I use json2csharp.com to convert any example output into a class which I just parse into the json library.

Claims transformation in ADFS 3.0 by making a call to REST API

We have a ASP.NET Web API (REST service) in our enterprise that gives us the list of coarse-grained claims for a user that we want to inject into the adfs token before passing the token onto the application. Does anyone know if making a rest call is possible using the Custom attribute store (by passing param's to the custom attribute store from the Claims rule language in ADFS 3.0) ?
Any help regarding this would be greatly appreciated!
Thanks,
Ady.
I'm able to make the REST call from the Custom Attribute store. For those who are still wondering about this can look at the below code.
using System;
using System.Collections.Generic;
using System.Text;
using System.IdentityModel;
using Microsoft.IdentityServer.ClaimsPolicy.Engine.AttributeStore;
using System.Net.Http;
using System.Net;
namespace CustomAttributeStores
{
public class CoarseGrainClaimsAttributeStore : IAttributeStore
{
#region Private Members
private string API_Server = "https://<Server Name>/API/";
#endregion
#region IAttributeStore Members
public IAsyncResult BeginExecuteQuery(string query, string[] parameters, AsyncCallback callback, object state)
{
string result = string.Empty;
if (parameters == null)
{
throw new AttributeStoreQueryFormatException("No query parameter.");
}
if (parameters.Length != 1)
{
throw new AttributeStoreQueryFormatException("More than one query parameter.");
}
string userName = parameters[0];
if (userName == null)
{
throw new AttributeStoreQueryFormatException("Query parameter cannot be null.");
}
//Ignore SSL Cert Error
//TODO: Need to set the SSL cert correctly for PROD Deployment
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
using (var client = new HttpClient())
{
//The url can be passed as a query
string serviceUrl = API_Server + "GetAdditionalClaim";
serviceUrl += "?userName=" + userName;
//Get the SAML token from the API
result = client
.GetAsync(serviceUrl)
.Result
.Content.ReadAsStringAsync().Result;
result = result.Replace("\"", "");
}
string[][] outputValues = new string[1][];
outputValues[0] = new string[1];
outputValues[0][0] = result;
TypedAsyncResult<string[][]> asyncResult = new TypedAsyncResult<string[][]>(callback, state);
asyncResult.Complete(outputValues, true);
return asyncResult;
}
public string[][] EndExecuteQuery(IAsyncResult result)
{
return TypedAsyncResult<string[][]>.End(result);
}
public void Initialize(Dictionary<string, string> config)
{
// No initialization is required for this store.
}
#endregion
}
}
No, you can't do this with the claims rules language.
This is somewhat of a hack but you could write the claims to e.g. a DB and then use a custom attribute store
If you have access to WIF on the client side, you can augment the claims there e.g. Adding custom roles to windows roles in ASP.NET using claims.