ASP.NET Core Identity GenerateTwoFactorTokenAsync returning empty string - c#

App is .NET Core 2.1.
I'm implementing SMS two-factor auth in my site (already have app-based tfa), using Twilio, and everything's ready to go, with the minor problem of not being able to generate the token.
The following code is what I call when the user enters their mobile number to set up TFA in the first place. Just generating a code and texting it to them. But the call to _userManager.GenerateTwoFactorTokenAsync is returning an empty string. Which, looking at the source code, it's hard-coded to do. Useful.
I was hoping to use that so I could use the same verification process I already have in place for my app-based tfa. So what function should I be using to generate tfa tokens to be sending to users? Or am I approaching this incorrectly?
EDIT: Just from my own digging, maybe I should be using the change phone number token for this initial setup. But then the question becomes, what should I use for the actual tfa process during login?
[Authorize]
[HttpPost("send-sms")]
public async Task<IActionResult> SendSms(SMSModel input)
{
var user = await _userManager.GetUserAsync(User);
if (String.IsNullOrWhiteSpace(_smsOptions.Sid) || String.IsNullOrWhiteSpace(_smsOptions.Token)) {
ModelState.AddModelError("phoneNumber", "SMS provider not set up.");
return BadRequest(ModelState);
}
var code = await _userManager.GenerateTwoFactorTokenAsync(user, _userManager.Options.Tokens.AuthenticatorTokenProvider);
var message = "Your one-time verification code is: " + code;
TwilioClient.Init(_smsOptions.Sid, _smsOptions.Token);
try {
var result = await MessageResource.CreateAsync(
to: new PhoneNumber(input.phone),
from: new PhoneNumber(_smsOptions.From),
body: message
);
} catch (Exception e) {
ModelState.AddModelError("phoneNumber", e.Message);
return BadRequest(ModelState);
}
return Ok();
}
Thanks in advance!

You can use await _userManager.GenerateTwoFactorTokenAsync(user, TokenOptions.DefaultPhoneProvider);
You can get details from docs,
how implement 2-factor auth in ASP.NET Core using sms

For anyone who comes along later, I ended up figuring it out. I just searched through the .NET core library and found the token provider PhoneNumberTokenProvider. Just passed that into the generate function instead of the Authentication one, and that seems to be working great.
Now I just need to figure out if there's a built-in way to save which kind of tfa a user is set up with, or if I need to manage that myself...

it's not work and return empty string when you try to get authenticator app token
you should to use under code to fix your problem
await _userManager.ResetAuthenticatorKeyAsync(user);
var token = await _userManager.GetAuthenticatorKeyAsync(user);
i use this code and work correctly
and you can see two factor authentication in identity source in this link

Related

Signalr User never shows authenticated

Added the latest SignalR (6.0.3) to my .net 6 API. Everything works there until I try to grab the user identity - it is always unauthorized. I'm really just trying to get a claim from the token, so maybe I'm looking in the wrong place.
The entire setup is localhost API supplying localhost Vue3 SPA.
Authorization works as intended for all API controller actions, following this tutorial.
SignalR Hub communicates as intended with front-end otherwise - receiving and sending.
The SignalR Hub app.MapHub<ChatHub>("/chat"); is the last line in Program.cs before app.Run();
I tried updating the connection from front-end to include accessTokenFactory function, and I can confirm token is correctly supplied here. However, this should not be necessary with cookie authentication.
In my SignalR hub class is a simple method for getting a particular claim: (the code is rough, just trying to get it to work)
public int GetPlayerId()
{
int id = 0;
try
{
var identity = (ClaimsIdentity)Context.User.Identity;
id = Int32.Parse(identity.FindFirst("playerId").ToString());
} catch (Exception ex)
{
return 0;
}
return id;
}
Context.User looks like this regardless of what I do:
I'm not sure where to even begin to debug, as authorization is working as intended across the entirety of the application otherwise. That would seem to point to a SignalR issue, but the few posts I could find about this were mostly severely outdated and made no discernable impact. According to the documentation, this should "just work" with the application's existing authorization.
Any insight on what to check into or additional details to provide is deeply appreciated.
Edit: Additional information
Adding the [Authorize] decorator to my hub class itself does not appear to work. I am able to send and receive regardless. Authorization continues to work as intended elsewhere.
The JwtMiddleware from the affore-linked authentication scheme did not affect the Context object of the SignalR hub.
Instead of just the accountId, I took the validated JWT token and added an identity to the HttpContext User. This is probably not perfect but I hope it help someone in the future:
var jwtToken = jwtUtils.ValidateJwtToken(token);
if (jwtToken != null)
{
int? accountId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);
if (accountId != null)
{
// attach account to context on successful jwt validation
context.Items["Account"] = await dataContext.Accounts.FindAsync(accountId);
context.User.AddIdentity(new ClaimsIdentity(jwtToken.Claims));
}
}

Identity server 4 handling Expired or revoked refresh tokens

I am working with an Identity server 4 system. We are using the exact code from the MvcHybridAutomaticRefresh sample
The issue is with this code here. AutomaticTokenManagementCookieEvents.cs#L73
var response = await _service.RefreshTokenAsync(refreshToken.Value);
if (response.IsError)
{
_logger.LogWarning("Error refreshing token: {error}", response.Error);
return;
}
Currently if a refesh token was revoked by the admins, or the refresh token has expired ( we do not have sliding refresh tokens enabled) Then the application will crash. I would expect it to reroute the user to the login screen.
I am i missing something in this sample that it cant handle that?
I have also posted this as a question on the issue forum #3599
current attempt
is to add The following rather where it detects the error
await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
which i had hoped would log the user out. This just hangs and never goes anywhere. Its not even logging you out of the server.
Current Solution
The only thing i can find currently that remotely works is to add a catch in the api call. This is not ideal as in our actual application we have a lot of api calls this would mean making a lot of changes to our application. Isnt there a way to force a login directly from the middle wear itself?
[Authorize]
public async Task<IActionResult> CallApi()
{
try
{
var token = await HttpContext.GetTokenAsync("access_token");
var client = _httpClientFactory.CreateClient();
client.SetBearerToken(token);
var response = await client.GetStringAsync(Constants.SampleApi + "identity");
ViewBag.Json = JArray.Parse(response).ToString();
return View();
}
catch (Exception)
{
return new SignOutResult(new[] { "Cookies", "oidc" });
}
}
You can add just one row to force the middleware to perform the challenge again:
if (response.IsError)
{
_logger.LogWarning("Error refreshing token: {error}", response.Error);
context.RejectPrincipal();
return;
}

Use google credentials to login into UWP C# app

I'm trying to make a login for a UWP app that I'm developing for a client that has a #<theircompay>.com email that uses G Suite. It doesn't have to access any user data, they just want it as an authentication so that only people that have a company email can access the app.
It would be great if they could login from within the app without having to use a web browser, and even better if it could remember them so they wouldn't have to login every single time.
I've been looking at OAuth 2.0 and several other solutions google has but can't really understand which one to use and much less how.
I looked into this answer but it doesn't seem like a good idea to ship your certificate file with your app.
So basically if this can be done, what (if any) certificates or credentials do I need to get from Google, and how would I handle them and the login through my C# code?
Edit
The app is 100% client side, no server backend
Taking a look at Google's GitHub it seems that .Net API is still not ready for UWP (however if you traverse the issues you will find that they are working on it, so it's probably a matter of time when official version is ready and this answer would be obsolete).
As I think getting simple accessToken (optionaly refresing it) to basic profile info should be sufficient for this case. Basing on available samples from Google I've build a small project (source at GitHub), that can help you.
So first of all you have to define your app at Google's developer console and obtain ClientID and ClientSecret. Once you have this you can get to coding. To obtain accessToken I will use a WebAuthenticationBroker:
string authString = "https://accounts.google.com/o/oauth2/auth?client_id=" + ClientID;
authString += "&scope=profile";
authString += $"&redirect_uri={RedirectURI}";
authString += $"&state={state}";
authString += $"&code_challenge={code_challenge}";
authString += $"&code_challenge_method={code_challenge_method}";
authString += "&response_type=code";
var receivedData = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.UseTitle, new Uri(authString), new Uri(ApprovalEndpoint));
switch (receivedData.ResponseStatus)
{
case WebAuthenticationStatus.Success:
await GetAccessToken(receivedData.ResponseData.Substring(receivedData.ResponseData.IndexOf(' ') + 1), state, code_verifier);
return true;
case WebAuthenticationStatus.ErrorHttp:
Debug.WriteLine($"HTTP error: {receivedData.ResponseErrorDetail}");
return false;
case WebAuthenticationStatus.UserCancel:
default:
return false;
}
If everything goes all right and user puts correct credentials, you will have to ask Google for tokens (I assume that you only want the user to put credentials once). For this purpose you have the method GetAccessToken:
// Parses URI params into a dictionary - ref: http://stackoverflow.com/a/11957114/72176
Dictionary<string, string> queryStringParams = data.Split('&').ToDictionary(c => c.Split('=')[0], c => Uri.UnescapeDataString(c.Split('=')[1]));
StringContent content = new StringContent($"code={queryStringParams["code"]}&client_secret={ClientSecret}&redirect_uri={Uri.EscapeDataString(RedirectURI)}&client_id={ClientID}&code_verifier={codeVerifier}&grant_type=authorization_code",
Encoding.UTF8, "application/x-www-form-urlencoded");
HttpResponseMessage response = await httpClient.PostAsync(TokenEndpoint, content);
string responseString = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
Debug.WriteLine("Authorization code exchange failed.");
return;
}
JsonObject tokens = JsonObject.Parse(responseString);
accessToken = tokens.GetNamedString("access_token");
foreach (var item in vault.RetrieveAll().Where((x) => x.Resource == TokenTypes.AccessToken.ToString() || x.Resource == TokenTypes.RefreshToken.ToString())) vault.Remove(item);
vault.Add(new PasswordCredential(TokenTypes.AccessToken.ToString(), "MyApp", accessToken));
vault.Add(new PasswordCredential(TokenTypes.RefreshToken.ToString(), "MyApp", tokens.GetNamedString("refresh_token")));
TokenLastAccess = DateTimeOffset.UtcNow;
Once you have the tokens (I'm saving them in PasswordVault for safety), you can later then use them to authenticate without asking the user for his credentials. Note that accessToken has limited lifetime, therefore you use refreshToken to obtain a new one:
if (DateTimeOffset.UtcNow < TokenLastAccess.AddSeconds(3600))
{
// is authorized - no need to Sign In
return true;
}
else
{
string token = GetTokenFromVault(TokenTypes.RefreshToken);
if (!string.IsNullOrWhiteSpace(token))
{
StringContent content = new StringContent($"client_secret={ClientSecret}&refresh_token={token}&client_id={ClientID}&grant_type=refresh_token",
Encoding.UTF8, "application/x-www-form-urlencoded");
HttpResponseMessage response = await httpClient.PostAsync(TokenEndpoint, content);
string responseString = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
JsonObject tokens = JsonObject.Parse(responseString);
accessToken = tokens.GetNamedString("access_token");
foreach (var item in vault.RetrieveAll().Where((x) => x.Resource == TokenTypes.AccessToken.ToString())) vault.Remove(item);
vault.Add(new PasswordCredential(TokenTypes.AccessToken.ToString(), "MyApp", accessToken));
TokenLastAccess = DateTimeOffset.UtcNow;
return true;
}
}
}
The code above is only a sample (with some shortcuts) and as mentioned above - a working version with some more error handling you will find at my GitHub. Please also note, that I haven't spend much time on this and it will surely need some more work to handle all the cases and possible problems. Though hopefully will help you to start.
Answer from Roamsz is great but didnt work for me because I found some conflicts or at least with the latest build 17134 as target, it doesn't work. Here are the problem, in his Github sample, he is using returnurl as urn:ietf:wg:oauth:2.0:oob . this is the type of url, you can't use with web application type when you create new "Create OAuth client ID" in the google or firebase console. you must use "Ios" as shown below. because web application requires http or https urls as return url.
from google doc
According to his sample he is using Client secret to obtain access token, this is not possible if you create Ios as type. because Android and Ios arent using client secret. It is perfectly described over here
client_secret The client secret obtained from the API Console. This
value is not needed for clients registered as Android, iOS, or Chrome
applications.
So you must use type as Ios, No Client Secret needed and return url is urn:ietf:wg:oauth:2.0:oob or urn:ietf:wg:oauth:2.0:oob:auto difference is that auto closes browser and returns back to the app. other one, code needs to be copied manually. I prefer to use urn:ietf:wg:oauth:2.0:oob:auto
Regarding code: please follow his github code. Just remove the Client Secret from the Access Token Request.
EDIT: it looks like I was right that even offical sample is not working after UWP version 15063, somebody created an issue on their github
https://github.com/Microsoft/Windows-universal-samples/issues/642
I'm using pretty straightforward code with Google.Apis.Oauth2.v2 Nuget package. Note, that I'm using v.1.25.0.859 of that package. I tried to update to the lastest version (1.37.0.1404), but this surprisingly doesn't work with UWP. At the same time v. 1.25.0.859 works just fine.
So, unless there's a better option, I would recommend to use a bit old, but working version of Nuget package.
This is my code:
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
new Uri("ms-appx:///Assets/User/Auth/google_client_secrets.json"),
new[] { "profile", "email" },
"me",
CancellationToken.None);
await GoogleWebAuthorizationBroker.ReauthorizeAsync(credential, CancellationToken.None);
Then you can retrieve access token from: credential.Token.AccessToken.

AuthenticationManager.GetExternalLoginInfoAsync() return null on Facebook [duplicate]

Update 2017!
The issue I had when I posted the original question has got nothing to do with the recent changes Facebook made when they forced everyone to version 2.3 of their API. For a solution to that specific problem, see sammy34's answer below. Version 2.3 of the /oauth/access_token endpoint now returns JSON instead of form-encoded values
For historical reasons, here's my original question/issue:
I've got an MVC5 Web application which is using the built-in support for authentication via Facebook and Google. When we built this app a few months ago, we followed this tutorial: http://www.asp.net/mvc/tutorials/mvc-5/create-an-aspnet-mvc-5-app-with-facebook-and-google-oauth2-and-openid-sign-on and everything worked great.
Now, all of a sudden, the Facebook authentication has just stopped working alltogether. The Google authentication still works great.
Description of the problem: We click the link to connect using Facebook, we are redirected to Facebook where we are prompted if we wan't to allow our Facebook app access to our profile. When we click "OK" we are redirected back to our site, but instead of being logged in we simply end up at the login screen.
I've gone through this process in debug mode and I've got this ActionResult in my account controller as per the tutorial mentioned above:
// GET: /Account/ExternalLoginCallback
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
............
When stepping through the code and upon returning from Facebook, the loginInfo object is always NULL, which causes the user to be redirected back to the login.
In order to understand what is actually happening behind the scenes, I installed Fiddler and monitored the HTTP traffic. What I disovered is that upon clicking "OK" at the Facebook permission dialog, Facebook redirects back to our application with this URL:
https://localhost/signin-facebook?code=<access-token>
This URL is not an actual file and probably handled by some controller/handler built into this OWIN framework I'm guessing. Most likely, it is connecting back to Facebook using the given code to query information about the user which is trying to login. Now, the problem is that instead of doing that, we are redirected to:
/Account/ExternalLoginCallback?error=access_denied
Which I'm sure is something Facebook is doing, that is, instead of giving us the user data, it's redirecting us back with this error message.
This causes the AuthenticationManager.GetExternalLoginInfoAsync(); to fail and always return NULL.
I'm completely out of ideas. As far as we know, we did not change anything on our end.
I've tried creating a new Facebook app, I've tried following the tutorial again but I always have the same problem.
Any ideas welcome!
Update!
OK, this is driving me insane! I've now manually gone through the steps required to perform the authentication and everything works great when I do that. Why on earth is this not working when using the MVC5 Owin stuff?
This is what I did:
// Step 1 - Pasted this into a browser, this returns a code
https://www.facebook.com/dialog/oauth?response_type=code&client_id=619359858118523&redirect_uri=https%3A%2F%2Flocalhost%2Fsignin-facebook&scope=&state=u9R1m4iRI6Td4yACEgO99ETQw9NAos06bZWilJxJrXRn1rh4KEQhfuEVAq52UPnUif-lEHgayyWrsrdlW6t3ghLD8iFGX5S2iUBHotyTqCCQ9lx2Nl091pHPIw1N0JV23sc4wYfOs2YU5smyw9MGhcEuinvTAEql2QhBowR62FfU6PY4lA6m8pD3odI5MwBYOMor3eMLu2qnpEk0GekbtTVWgQnKnH6t1UcC6KcNXYY
I was redirected back to localhost (which I had shut down at this point to avoid being redirected immediately away). The URL I was redirected to is this:
https://localhost/signin-facebook?code=<code-received-removed-for-obvious-reasons>
Now, I grabbed the code I got and used it in the URL below:
// Step 2 - opened this URL in a browser, and successfully retrieved an access token
https://graph.facebook.com/oauth/access_token?client_id=619359858118523&redirect_uri=https://localhost/signin-facebook&client_secret=<client-secret>&code=<code-from-step-1>
// Step 3 - Now I'm able to query the facebook graph using the access token from step 2!
https://graph.facebook.com/me?access_token=<access-token-from-step-2>
No errors, everything works great! Then why the hell is this not working when using the MVC5 Owin stuff? There's obviously something wrong with the OWin implementation.
Update 22nd April 2017: Version 3.1.0 of the Microsoft.Owin.* packages are now available. If you're having problems after Facebook's API changes from the 27th March 2017, try the updated NuGet packages first. In my case they solved the problem (working fine on our production systems).
Original answer:
In my case, I woke up on the 28th March 2017 to discover that our app's Facebook authentication had suddenly stopped working. We hadn't changed anything in the app code.
It turns out that Facebook did a "force upgrade" of their graph API from version 2.2 to 2.3 on 27th March 2017. One of the differences in these versions of the API seems to be that the Facebook endpoint /oauth/access_token responds no longer with a form-encoded content body, but with JSON instead.
Now, in the Owin middleware, we find the method protected override FacebookAuthenticationHandler.AuthenticateCoreAsync(), which parses the body of the response as a form and subsequently uses the access_token from the parsed form. Needless to say, the parsed form is empty, so the access_token is also empty, causing an access_denied error further down the chain.
To fix this quickly, we created a wrapper class for the Facebook Oauth response
public class FacebookOauthResponse
{
public string access_token { get; set; }
public string token_type { get; set; }
public int expires_in { get; set; }
}
Then, in OwinStart, we added a custom back-channel handler...
app.UseFacebookAuthentication(new FacebookAuthenticationOptions
{
AppId = "hidden",
AppSecret = "hidden",
BackchannelHttpHandler = new FacebookBackChannelHandler()
});
...where the handler is defined as:
public class FacebookBackChannelHandler : HttpClientHandler
{
protected override async System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
var result = await base.SendAsync(request, cancellationToken);
if (!request.RequestUri.AbsolutePath.Contains("access_token"))
return result;
// For the access token we need to now deal with the fact that the response is now in JSON format, not form values. Owin looks for form values.
var content = await result.Content.ReadAsStringAsync();
var facebookOauthResponse = JsonConvert.DeserializeObject<FacebookOauthResponse>(content);
var outgoingQueryString = HttpUtility.ParseQueryString(string.Empty);
outgoingQueryString.Add(nameof(facebookOauthResponse.access_token), facebookOauthResponse.access_token);
outgoingQueryString.Add(nameof(facebookOauthResponse.expires_in), facebookOauthResponse.expires_in + string.Empty);
outgoingQueryString.Add(nameof(facebookOauthResponse.token_type), facebookOauthResponse.token_type);
var postdata = outgoingQueryString.ToString();
var modifiedResult = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(postdata)
};
return modifiedResult;
}
}
Basically, the handler simply creates a new HttpResponseMessage containing the equivalent form-encoded information from the Facebook JSON response. Note that this code uses the popular Json.Net package.
With this custom handler, the problems seem to be resolved (although we're yet to deploy to prod :)).
Hope that saves somebody else waking up today with similar problems!
Also, if anybody has a cleaner solution to this, I'd love to know!
Noticed this problem yesterday. Facebook does not support Microsoft.Owin.Security.Facebook version 3.0.1 anymore. For me it worked to install version 3.1.0. To update to 3.1.0, run the command Install-Package Microsoft.Owin.Security.Facebook in Package Manager Console: https://www.nuget.org/packages/Microsoft.Owin.Security.Facebook
Ok I've got a solution to the problem.
This is the code I had previously in my Startup.Auth.cs file:
var x = new FacebookAuthenticationOptions();
//x.Scope.Add("email");
x.AppId = "1442725269277224";
x.AppSecret = "<secret>";
x.Provider = new FacebookAuthenticationProvider()
{
OnAuthenticated = async context =>
{
//Get the access token from FB and store it in the database and
//use FacebookC# SDK to get more information about the user
context.Identity.AddClaim(new System.Security.Claims.Claim("FacebookAccessToken",context.AccessToken));
context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:name", context.Name));
context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:email", context.Email));
}
};
x.SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie;
app.UseFacebookAuthentication(x);
Notice how the
x.Scope.Add("email")
line has been commented out, but still I'm query-ing for the e-mail later in the OnAuthenticated handler? Yup, that's right. For some reason this worked flawlessly for a few weeks.
My solution was to simply uncomment the x.Scope.Add("email"); line to make sure that the scope=email variable was present in the initial request to Facebook.
Now everything works like it did!
I cannot understand why this worked before like it was. The only explanation I can come up with is that Facebook changed something on their end.
I had this same issue with the Google Authentication. The following worked for me: Changes to Google OAuth 2.0 and updates in Google middleware for 3.0.0 RC release
The last Facebook upgrade was on 2015-02-09 (https://www.nuget.org/packages/Microsoft.AspNet.WebPages.OAuth/)
The latest version of the API at that point was version 2.2. Version 2.2 expired on the 25th of March 2017, which is coincidentally when the problem started. (https://developers.facebook.com/docs/apps/changelog)
I'm guessing Facebook probably automatically upgraded the API and now the MS OAUTH library is unable to parse the new response.
tldr: The Microsoft WebPages OAuth library is outdated (for FB at least) and you'll probably have to find another solution
The above solutions didn't work for me. In the end, it seemed to be related to the Session. By "waking up" the session in the previous call, it would no longer return null from the GetExternalLoginInfoAsync()
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string returnUrl)
{
Session["WAKEUP"] = "NOW!";
// Request a redirect to the external login provider
return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
}
Like the OP, I had the 3rd party auth working fine for a long time then suddenly it stopped. I beleive it was due to the changes made in my code when I set up the Session to use Redis Cache on Azure.
I had this problem as well, but it wasn't caused by the scope setting. Took me a long time to figure that out, but what finally clued me in was by setting a custom logger by setting the following in OwinStartup.Configuration(IAppBuilder app).
app.SetLoggerFactory(new LoggerFactory());
// Note: LoggerFactory is my own custom ILoggerFactory
This outputted the following:
2014-05-31 21:14:48,508 [8] ERROR
Microsoft.Owin.Security.Cookies.CookieAuthenticationMiddleware
[(null)] - 0x00000000 - Authentication failed
System.Net.Http.HttpRequestException: An error occurred while sending
the request. ---> System.Net.WebException: The remote name could not
be resolved: 'graph.facebook.com' at
System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
at System.Net.Http.HttpClientHandler.GetResponseCallback(IAsyncResult
ar) --- End of inner exception stack trace --- at
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task
task) at
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task
task) at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at
Microsoft.Owin.Security.Facebook.FacebookAuthenticationHandler.d__0.MoveNext()
Based on the above call stack I found that my Azure VM was unable to resolve graph.facebook.com. All I had to do to fix that was to run "ipconfig /registerdns" and I was all fixed...
I have been working on solution for three days. And I've just found it on github(https://github.com/aspnet/AspNetKatana/issues/38#issuecomment-290400987)
var facebookOptions = new FacebookAuthenticationOptions()
{
AppId = "xxxxx",
AppSecret = "xxxxx",
};
// Set requested scope
facebookOptions.Scope.Add("email");
facebookOptions.Scope.Add("public_profile");
// Set requested fields
facebookOptions.Fields.Add("email");
facebookOptions.Fields.Add("first_name");
facebookOptions.Fields.Add("last_name");
facebookOptions.Provider = new FacebookAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
// Attach the access token if you need it later on for calls on behalf of the user
context.Identity.AddClaim(new System.Security.Claims.Claim("FacebookAccessToken", context.AccessToken));
foreach (var claim in context.User)
{
//var claimType = string.Format("urn:facebook:{0}", claim.Key);
var claimType = string.Format("{0}", claim.Key);
string claimValue = claim.Value.ToString();
if (!context.Identity.HasClaim(claimType, claimValue))
context.Identity.AddClaim(new System.Security.Claims.Claim(claimType, claimValue, "XmlSchemaString", "Facebook"));
}
return Task.FromResult(0);
}
};
app.UseFacebookAuthentication(facebookOptions);
And to get values
var info = await AuthenticationManager.GetExternalLoginInfoAsync();
if (info != null)
{
var firstName = info.ExternalIdentity.Claims.First(c => c.Type == "first_name").Value;
var lastName = info.ExternalIdentity.Claims.First(c => c.Type == "last_name").Value;
}
Check you get an outside internet connection from your application. If not, fix your outside internet connection. My problem was I was using an EC2 AWS instance that suddenly stopped connecting to the internet. It took me a while to realize that was the problem.
This drove me insane. All was working until I deployed to my staging environment. I was using Microsoft.Owin.Security.Facebook version 3.0.1 from Nuget. Updated it to the prelease version 3.1.0 from Nuget and I no longer got the access denied error...
Even though i did everything what sammy34 said, it did not work for me. I was at the same point with HaukurHaf: When i make apirequest manually on browser it works perfect, but if i use my mvc app, GetExternalLoginInfoAsync() always returns null.
So i changed some rows on sammy34's codes like on this comment: https://stackoverflow.com/a/43148543/7776015
Replaced:
if (!request.RequestUri.AbsolutePath.Contains("/oauth"))
{
request.RequestUri = new Uri(request.RequestUri.AbsoluteUri.Replace("?access_token", "&access_token"));
}
var result = await base.SendAsync(request, cancellationToken);
if (!request.RequestUri.AbsolutePath.Contains("/oauth"))
{
return result;
}
Instead of:
var result = await base.SendAsync(request, cancellationToken);
if (!request.RequestUri.AbsolutePath.Contains("access_token"))
return result;
And added this row into my FacebookAuthenticationOptions:
UserInformationEndpoint = "https://graph.facebook.com/v2.8/me?fields=id,name,email,first_name,last_name,picture"
and now it works.(fields and that parameters optional)
Note: I did not update Microsoft.Owin.Security.Facebook

Persistent authentication across UWP app and Azure Mobile Service

Building on the example here I'm attempting to authenticate an MSA login on the client, and have it authenticate service-side as well. The difference with mine is I'm using the new WebAccount-related API's in Windows 10 instead of the now deprecated Live SDK.
So far I've got:
var provider = await WebAuthenticationCoreManager.FindAccountProviderAsync("https://login.microsoft.com", "consumers");
var request = new WebTokenRequest(provider, "service::wl.basic wl.emails::DELEGATION", "none");
var result = await WebAuthenticationCoreManager.RequestTokenAsync(request);
if (result.ResponseStatus == WebTokenRequestStatus.Success)
{
string token = result.ResponseData[0].Token;
//This calls my custom wrappers around the Live REST API v5 and runs successfully with this token
var acc = await LiveApi.GetLiveAccount(token);
var jtoken = new JObject
{
{"authenticationToken", token}
};
try
{
//Shouldn't this work? but raises a 401
await App.MobileService.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount, jtoken);
//Alternate method? Also raises a 401
//await App.MobileService.LoginWithMicrosoftAccountAsync(token);
}
}
As I mentioned in the comments, all I get are 401s.
As far as I can tell the application is configured correctly in Microsoft Account dev center:
I'm using the client ID and secret from the same app in the Azure portal.
JWT issuing is not restricted.
Redirect URL is of the format https://{appname}.azurewebsites.net/.auth/login/microsoftaccount/callback
Authentication works fine when I switch to use purely server-side authentication. i.e.
await App.MobileService.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount);
Any ideas? Am I missing something? Any help would be appreciated.
UPDATED:
The token I get back in the WebTokenRequestResult is 877 characters long and does not appear to be in the JWT format, with the dot (.) separators and I'm quite certain that this is the issue. The following error gets logged in service when the client calls the code above:
JWT validation failed: IDX10708: 'System.IdentityModel.Tokens.JwtSecurityTokenHandler' cannot read this string: 'EwCQAq1DBAAUGCCXc8wU/zFu9QnLdZXy+...Zz9TbuxCowNxsEPPOvXwE='.
Application: The string needs to be in compact JSON format, which is of the form: '<Base64UrlEncodedHeader>.<Base64UrlEndcodedPayload>.<OPTIONAL, Base64UrlEncodedSignature>'..
Application: 2015-12-07T17:47:09 PID[5740] Information Sending response: 401.71 Unauthorized
What format is the token currently in? Can it be transformed to a JWT?
Still no closer to a solution, so any help is appreciated.
Anyone feel free to correct me, but it looks like RequestTokenAsync gets you an access token which you can't use to login the backend. You need an authentication token for that, and as far as I can see RequestTokenAsync doesn't get you that.
There's some info here about the tokens.
If people end up here searching for a solution for App Service Mobile, the update to MobileService. Then there is now a solution
The code replicated here is:
async Task<string> GetDataAsync()
{
try
{
return await App.MobileService.InvokeApiAsync<string>("values");
}
catch (MobileServiceInvalidOperationException e)
{
if (e.Response.StatusCode != HttpStatusCode.Unauthorized)
{
throw;
}
}
// Calling /.auth/refresh will update the tokens in the token store
// and will also return a new mobile authentication token.
JObject refreshJson = (JObject)await App.MobileService.InvokeApiAsync(
"/.auth/refresh",
HttpMethod.Get,
null);
string newToken = refreshJson["authenticationToken"].Value<string>();
App.MobileService.CurrentUser.MobileServiceAuthenticationToken
= newToken;
return await App.MobileService.InvokeApiAsync<string>("values");
}
Hope it saves somebody time !

Categories