I've got a Xamarin.Forms app (iOS, Android, UWP) connected to Azure Mobile Apps' authentication service. From what I read, it seemed pretty straightforward to implement. I'm testing in a UWP project, and Android, both get the same result.
When logging in, I get this lovely "can't connect to the service you need right now." I don't see anything wrong in my code. What might be going on here?
Windows UWP:
public async Task<bool> Authenticate()
{
string message = string.Empty;
var success = false;
try
{
// Sign in with Facebook login using a server-managed flow.
if (user == null)
{
user = await TaskService.DefaultService.CurrentClient.LoginAsync(MobileServiceAuthenticationProvider.Facebook);
if (user != null)
{
success = true;
message = string.Format("You are now signed-in as {0}.", user.UserId);
}
}
}
catch (Exception ex)
{
message = string.Format("Authentication Failed: {0}", ex.Message);
}
// Display the success or failure message.
await new MessageDialog(message, "Sign-in result").ShowAsync();
return success;
}
Android:
public async Task<bool> Authenticate()
{
var success = false;
var message = string.Empty;
try
{
// Sign in with Facebook login using a server-managed flow.
user = await TaskService.DefaultService.CurrentClient.LoginAsync(this,
MobileServiceAuthenticationProvider.Facebook);
if (user != null)
{
message = string.Format("you are now signed-in as {0}.",
user.UserId);
success = true;
}
}
catch (Exception ex)
{
message = ex.Message;
}
// Display the success or failure message.
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.SetMessage(message);
builder.SetTitle("Sign-in result");
builder.Create().Show();
return success;
}
Can't connect to service error:
The settings on Facebook for the app:
Visited:
https://azure.microsoft.com/en-us/documentation/articles/app-service-mobile-xamarin-forms-get-started-users/
https://azure.microsoft.com/en-us/documentation/articles/app-service-mobile-how-to-configure-facebook-authentication/
https://developer.xamarin.com/guides/xamarin-forms/web-services/authentication/azure/
EDIT:
I've tried moving the client code to App.cs:
private static MobileServiceClient _Client;
public static MobileServiceClient Client
{
get
{
if(_Client == null) _Client = new MobileServiceClient(AppConstants.AzureMobileServiceURL);
return _Client;
}
set { _Client = value; }
}
And the new call in MainPage.xaml.cs:
uesr = await Slated.App.Client.LoginAsync(MobileServiceAuthenticationProvider.Facebook);
Same result!
Another note: My Azure Mobile Services URL does include https://
EDIT 2:
Code below for logging on backend, looks to be an issue when redirecting to /login/facebook, the rest of the authentication appears to proceed OK. FYI - replaced the sensitive data with ____
2016-07-29T18:38:43 PID[6684] Verbose Received request: GET https://________.azurewebsites.net/login/facebook
2016-07-29T18:38:43 PID[6684] Information Redirecting: https://www.facebook.com/dialog/oauth?response_type=code&client_id=_____________&redirect_uri=https%3A%2F%2F________.azurewebsites.net%2F.auth%2Flogin%2Ffacebook%2Fcallback&scope=public_profile&state=_____________________________&display=popup
2016-07-29T18:38:50 PID[6684] Verbose Received request: GET https://________.azurewebsites.net/.auth/login/facebook/callback?code=____________________
2016-07-29T18:38:50 PID[6684] Verbose Calling into external HTTP endpoint GET https://graph.facebook.com/oauth/access_token.
2016-07-29T18:38:51 PID[6684] Verbose Calling into external HTTP endpoint GET https://graph.facebook.com/oauth/access_token.
2016-07-29T18:38:51 PID[6684] Verbose Calling into external HTTP endpoint GET https://graph.facebook.com/me.
2016-07-29T18:38:51 PID[6684] Information Login completed for 'Thomas Gardner'. Provider: 'facebook'.
2016-07-29T18:38:51 PID[6684] Verbose Writing 'AppServiceAuthSession' cookie for site '________.azurewebsites.net'. Length: 512.
2016-07-29T18:38:51 PID[6684] Information Redirecting: https://________.azurewebsites.net/login/facebook
2016-07-29T18:38:51 PID[6684] Verbose Received request: GET https://________.azurewebsites.net/login/facebook
2016-07-29T18:38:51 PID[6684] Verbose Found 'AppServiceAuthSession' cookie for site '________.azurewebsites.net'. Length: 512.
2016-07-29T18:38:51 PID[6684] Verbose Authenticated Thomas Gardner successfully using 'Session Cookie' authentication.
2016-07-29T18:38:52 PID[6684] Verbose Received request: GET https://________.azurewebsites.net/login/facebook
2016-07-29T18:38:52 PID[6684] Verbose Found 'AppServiceAuthSession' cookie for site '________.azurewebsites.net'. Length: 512.
2016-07-29T18:38:52 PID[6684] Verbose Authenticated __________ successfully using 'Session Cookie' authentication.
2016-07-29T18:38:52 PID[6684] Information Request, Method=GET, Url=https://________.azurewebsites.net/login/facebook, Message='https://________.azurewebsites.net/login/facebook'
2016-07-29T18:38:52 PID[6684] Information Message='Will use same 'JsonMediaTypeFormatter' formatter', Operation=JsonMediaTypeFormatter.GetPerRequestFormatterInstance
2016-07-29T18:38:52 PID[6684] Information Message='Selected formatter='JsonMediaTypeFormatter', content-type='application/json; charset=utf-8'', Operation=DefaultContentNegotiator.Negotiate
2016-07-29T18:38:52 PID[6684] Information Response, Status=404 (NotFound), Method=GET, Url=https://________.azurewebsites.net/login/facebook, Message='Content-type='application/json; charset=utf-8', content-length=unknown'
Backend Code: It is copied from MSFT's template code. Not much has been done to the startup.
Startup.Mobile.App.cs
public partial class Startup
{
public static void ConfigureMobileApp(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
//For more information on Web API tracing, see http://go.microsoft.com/fwlink/?LinkId=620686
config.EnableSystemDiagnosticsTracing();
new MobileAppConfiguration()
.UseDefaultConfiguration()
.ApplyTo(config);
// Use Entity Framework Code First to create database tables based on your DbContext
Database.SetInitializer(new SlatedInitializer());
// To prevent Entity Framework from modifying your database schema, use a null database initializer
// Database.SetInitializer<SlatedContext>(null);
MobileAppSettingsDictionary settings = config.GetMobileAppSettingsProvider().GetMobileAppSettings();
if (string.IsNullOrEmpty(settings.HostName))
{
// This middleware is intended to be used locally for debugging. By default, HostName will
// only have a value when running in an App Service application.
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = ConfigurationManager.AppSettings["SigningKey"],
ValidAudiences = new[] { ConfigurationManager.AppSettings["ValidAudience"] },
ValidIssuers = new[] { ConfigurationManager.AppSettings["ValidIssuer"] },
TokenHandler = config.GetAppServiceTokenHandler()
});
}
app.UseWebApi(config);
}
}
public class SlatedInitializer : CreateDatabaseIfNotExists<SlatedContext>
{
protected override void Seed(SlatedContext context)
{
/*List<Tasks> todoItems = new List<Tasks>
{
new Tasks { Id = Guid.NewGuid().ToString(), Text = "First item", Complete = false },
new Tasks { Id = Guid.NewGuid().ToString(), Text = "Second item", Complete = false },
};
foreach (Tasks todoItem in todoItems)
{
context.Set<Tasks>().Add(todoItem);
}*/
base.Seed(context);
}
}
From the URIs that are being used, it looks like you are mixing packages between Mobile Services and Mobile Apps. The two are not compatible.
To learn more, see Client and server versioning in Mobile Apps and Mobile Services.
On the server, you should be using Microsoft.Azure.Mobile.Server.*. Make sure you have no packages in the form WindowsAzure.MobileServices.Backend.
On the client, you must use the package Microsoft.Azure.Mobile.Client.
user = await App.MobileService .LoginAsync(MobileServiceAuthenticationProvider.Facebook);
try this line of code.This should work and for the user id
you can simply do it like that :
var userId = user.Id;
Well here how I do it : in the app.xaml.cs
public static MobileServiceClient MobileService = new MobileServiceClient("https://yourmobileservices.azurewebsites.net");
then in your code behind:
user = await App.MobileService.LoginAsync(MobileServiceAuthenticationProvider.Facebook);
and it works like a charm for me,And make sure that in azure portal in the
Authentication tab that under advanced settings that Token store is on.
Related
I have an android app that needs to make a call to a asp.net core web api server.
I am using RestSharp to make the request.
Here is the code generating the request:
public LoginResponse SignInWithGoogle(string token)
{
//Api request for token
RestRequest request = new RestRequest("login/google", Method.POST);
request.AddJsonBody(new { Token = token });
//request.AddParameter("token", token, ParameterType.GetOrPost);
var response = restClient.Execute<LoginResponse>(request);
if (response.ErrorException != null)
{
throw new Exception("The APi request failed. See inner exception for more details", response.ErrorException);
}
AuthenticationToken = response.Data.token;
restClient.Authenticator = authenticator;
return response.Data;
}
Here is the web api code:
[AllowAnonymous]
[HttpPost]
[Route("google")]
public IActionResult GoogleLogin([FromBody] GoogleLoginDto data)
{
GoogleJsonWebSignature.Payload payload;
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
SigningCredentials creds = new SigningCredentials(Global.symmetricSecurityKey, SecurityAlgorithms.HmacSha256);
//Try to validate the Google token.
try
{
payload = GoogleJsonWebSignature.ValidateAsync(data.Token).Result;
}
catch (Exception e)
{
return Unauthorized();
}
...
}
GoogleLoginDto contains one property Token that is public.
The problem is that I get a 404. It seems to me that the JSON in the request is not being serialized to GoogleLoginDto but I can't find out why... I'm guessing because the API can't find the data field and so thinks i'm asking for a route that doesn't exist...
I also tried doing request.AddParameter("token", token, ParameterType.GetOrPost); as you can see, but I get an exception saying that Content-Type can't be null.
I thought about adding the Content-Type header but that seems ridiculous because RestSharp is supposed to determine that automatically...
Can anyone see anything I'm missing here? Thanks.
The API is unable to map the provided URL to a controller action. That is what it is 404 Not Found. Nothing to do with the data. It is the URL.
Given that the desired URL is login/google, ensure that the target controller has the proper routes defined that would allow the request to be mapped to the correct actions.
[Route("login")] // Route prefix
public class LoginController : Controller {
[AllowAnonymous]
[HttpPost("google")] // Matches POST login/google
public async Task<IActionResult> GoogleLogin([FromBody] GoogleLoginDto data) {
if(ModelState.IsValid) {
GoogleJsonWebSignature.Payload payload;
var tokenHandler = new JwtSecurityTokenHandler();
var creds = new SigningCredentials(Global.symmetricSecurityKey, SecurityAlgorithms.HmacSha256);
//Try to validate the Google token.
try {
payload = await GoogleJsonWebSignature.ValidateAsync(data.Token);
} catch (Exception e) {
return Unauthorized();
}
return Ok();
}
return BadRequest();
}
}
I'm attempting to design a system where users are created in my AWS user pool and assigned to one of four user groups. These user groups have roles attached to them which specify the API Calls they are allowed to make. I've created a user for each group and I'm able to successfully log into them in my Android Application. My User Pool is also attached to an Identity Pool for handling Single Sign On with Identity Federation.
The problem is that rather than assuming the Role assigned to the user group, when I log into the user, the role assigned to the user seems to be coming from the Identity Pool rather than their User Group, and as a result they're unable to make the api calls that they should have access to.
I'm attempting to fix this by implementing a Custom Authorizer in Node.js, but the script appears to be running into some problems. Whenever it enters the ValidateToken() method, it fails saying that the token isn't a JWT token.
console.log('Loading function');
var jwt = require('jsonwebtoken');
var request = require('request');
var jwkToPem = require('jwk-to-pem');
var groupName = 'MY_GROUP_NAME';
var roleName = 'MY_ROLE_NAME';
var policyName = 'MY_POLICY_NAME';
var userPoolId = 'MY_USER_POOL_ID';
var region = 'MY_REGION';
var iss = 'https://cognito-idp.' + region + '.amazonaws.com/' + userPoolId;
var pems;
exports.handler = function(event, context) {
//Download PEM for your UserPool if not already downloaded
if (!pems) {
//Download the JWKs and save it as PEM
request({
url: iss + '/.well-known/jwks.json',
json: true
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
pems = {};
var keys = body['keys'];
for(var i = 0; i < keys.length; i++) {
//Convert each key to PEM
var key_id = keys[i].kid;
var modulus = keys[i].n;
var exponent = keys[i].e;
var key_type = keys[i].kty;
var jwk = { kty: key_type, n: modulus, e: exponent};
var pem = jwkToPem(jwk);
pems[key_id] = pem;
}
//Now continue with validating the token
ValidateToken(pems, event, context);
} else {
//Unable to download JWKs, fail the call
context.fail("error");
}
});
} else {
//PEMs are already downloaded, continue with validating the token
ValidateToken(pems, event, context);
};
};
function ValidateToken(pems, event, context) {
var token = event.authorizationToken;
//Fail if the token is not jwt
var decodedJwt = jwt.decode(token, {complete: true});
if (!decodedJwt) {
//THIS IS WHERE THE SCRIPT ENDS UP
console.log("Not a valid JWT token");
context.fail("Unauthorized - Invalid Token Provided");
return;
}
//Fail if token is not from your UserPool
if (decodedJwt.payload.iss != iss) {
console.log("invalid issuer");
context.fail("Unauthorized - Invalid Issuer Provided");
return;
}
//Reject the jwt if it's not an 'Access Token'
if (decodedJwt.payload.token_use != 'access') {
console.log("Not an access token");
context.fail("Unauthorized - Not an Access Token");
return;
}
//Get the kid from the token and retrieve corresponding PEM
var kid = decodedJwt.header.kid;
var pem = pems[kid];
if (!pem) {
console.log('Invalid access token');
context.fail("Unauthorized - Invalid Access Token Provided");
return;
}
//Verify the signature of the JWT token to ensure it's really coming from your User Pool
jwt.verify(token, pem, { issuer: iss }, function(err, payload) {
if(err) {
console.log(err, err.stack); // an error occurred
context.fail("Unauthorized - Could not verify token signature");
}
else {
//Valid token. Generate the API Gateway policy for the user
//Always generate the policy on value of 'sub' claim and not for 'username' because username is reassignable
//sub is UUID for a user which is never reassigned to another user.
var principalId = payload.sub;
var username = payload.username;
var cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
var params = {
UserPoolId: userPoolId, /* ID of the Target User Pool */
Username: username, /* Provided by event object??? */
Limit: 0,
NextToken: '' //May need actual token value
};
cognitoidentityserviceprovider.adminListGroupsForUser(params, function(err, data) {
if (err){
console.log(err, err.stack); // an error occurred
context.fail("Unauthorized - Could not obtain Groups for User");
}
else{
var groups = data.Groups;
var numGroups = groups.length;
var isFound = false;
for(var i = 0; i < numGroups; i++){
if(groups[i].GroupName == groupName){
isFound = true;
}
}
if(isFound){
var iam = new AWS.IAM();
var iamParams = {
PolicyName: policyName, /* Name of the Policy in the User Group Role */
RoleName: roleName /* Name of the User Group Role */
};
iam.getRolePolicy(params, function(err, data) {
if (err){
console.log(err, err.stack); // an error occurred
context.fail("Unauthorized - Could not acquire Policy for User Group Role");
}
else {
var policy = data.PolicyDocument;
context.succeed(policy); //May need to build policy
}
});
}
else{
context.fail("Unauthorized - Could not find the required User Group under the User");
}
}
});
}
});
}
Can anybody identify the problem with this script, or perhaps help me identify why the tokens being set aren't valid JWT tokens? The tokens are sent by an Android Application using the AWS Cognito SDK.
EDIT: Upon further investigation, the token retrieved from event.authorizationToken is of the following format (the [VALUE] blocks are to hide potentially sensitive information):
AWS4-HMAC-SHA256 Credential=[VALUE1]/20170329/us-east-1/execute-api/aws4_request,
SignedHeaders=host;x-amz-date;x-amz-security-token,
Signature=[VALUE2]
If clients are getting the AWS credentials after login, you can only use AWS_IAM authorization type on the API Gateway Methods. The authorizationToken value you are seeing is the AWS signature generated by the client using the credentials vended by Cognito. It will not be possible for you to validate the AWS signature in a custom authorizer.
Are you following this Cognito blog post? If so, I think you might be confusing the User Group role with the authenticated role selection on the Identity Pool. When you use the federated identities with User Pool provider, your client will get back AWS credentials that have the permissions of the 'Authenticated role' from that section in the Cognito tab in the Identity Pool. In the blog post this would be the 'EngineerRole' set on the Identity Pool.
I figured it out:
This document (specifically the bottom part) says "If you set roles for groups in an Amazon Cognito user pool, those roles are passed through the user's ID token. To use these roles, you must also set Choose role from token for the authenticated role selection for the identity pool."
All it takes is to set the appropriate Trust Policy on each role, adjust the Identity Pool to use "Choose role from token" with the user pool authentication provider, and the proper roles are now being assumed. For others running into this problem, here is my trust policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Federated": "cognito-identity.amazonaws.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "[IDENTITY_POOL_ID]"
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "authenticated"
}
}
}
]
}
I had a working app with Facebook & Email Login feature, since I upgrade the Firebase console (only, the sdk has not been update).
The app release before the Firebase 3.0 was working before, but it is not anymore able to sign/log with Facebook after the console has been upgraded.
What I have done:
1 - Upgraded the Firebase console
Because of Firebase & Facebook console update, I also had to put the Oauth Callback to the Facebook App
2 - Pasted the Firebase Facebook OAuth Callback to the Facebook console (before it was void) `https://xxx.firebaseapp.com/__/auth/handler``
The Exception:
The firebase Auth listener trigger a Firebase Error :
Invalid authentication credentials provided. and Facebook :
{"providerErrorInfo":{"code":400,"message":"Unsuccessful debug_token
response from Facebook: {\"error\":{\"message\":\"(#100) You must
provide an app access token or a user access token that is an owner or
developer of the
app\",\"type\":\"OAuthException\",\"code\":100,\"fbtrace_id\":\"DG4lLRJHFBS\"}}"}}
The FirebaseError Code:
In the decompiled code of the FirebaseAndroidSdk, the error object is:
0 = {java.util.LinkedHashMap$LinkedEntry#22680} "code" ->
"INVALID_CREDENTIALS"
1 = {java.util.LinkedHashMap$LinkedEntry#22681}
"message" -> "Invalid authentication credentials provided."
2 = {java.util.LinkedHashMap$LinkedEntry#22682} "details" ->
"{"providerErrorInfo":{"code":400,"message":"Unsuccessful debug_token
response from Facebook: {\"error\":{\"message\":\"(#100) You must
provide an app access token or a user access token that is an owner or
developer of the app\",\"type\":\"OAuthException\",\"code\":100,\"fbtrace_id\":\"BtB3JF2qmku\"}}"}}"
with the decompiled code:
private void makeAuthenticationRequest(String urlPath, Map<String, String> params, AuthResultHandler handler) {
final AuthenticationManager.AuthAttempt attempt = this.newAuthAttempt(handler);
this.makeRequest(urlPath, HttpRequestType.GET, params, Collections.emptyMap(), new RequestHandler() {
public void onResult(Map<String, Object> result) {
Object errorResponse = result.get("error");
String token = (String)Utilities.getOrNull(result, "token", String.class);
if(errorResponse == null && token != null) {
if(!AuthenticationManager.this.attemptHasBeenPreempted(attempt)) {
AuthenticationManager.this.authWithCredential(token, result, attempt);
}
} else {
FirebaseError error = AuthenticationManager.this.decodeErrorResponse(errorResponse);
AuthenticationManager.this.fireAuthErrorIfNotPreempted(error, attempt);
}
}
public void onError(IOException e) {
FirebaseError error = new FirebaseError(-24, "There was an exception while connecting to the authentication server: " + e.getLocalizedMessage());
AuthenticationManager.this.fireAuthErrorIfNotPreempted(error, attempt);
}
});
}
At AuthListener level, the firebaseError code : -20
https://www.firebase.com/docs/java-api/javadoc/com/firebase/client/FirebaseError.html
The specified authentication credentials are invalid.
The Facebook Error Code:
code 400
Nothing relevant found here : https://developers.facebook.com/docs/graph-api/using-graph-api/#errors
The code for Authing:
public void authWithFirebase(final String provider, Map<String, String> options) {
if (options.containsKey(AUTH_OPTIONS_ERROR)) {
EventBus.getDefault().post(new MessageToDisplayEvent(options.get(AUTH_OPTIONS_ERROR), true));
} else {
if (provider.equalsIgnoreCase(AUTH_PROVIDER_TWITTER)) {
// if the provider is twitter, we must pass in additional options, so use the options endpoint
ref.authWithOAuthToken(provider, options, new AuthResultHandler(provider));
} else {
// if the provider is not twitter, we just need to pass in the oauth_token
ref.authWithOAuthToken(provider, options.get(AUTH_OPTIONS_TOKEN), new AuthResultHandler(provider));
}
}
}
TOKEN Validity:
From the code above, the Token is confirmed valid since :
https://graph.facebook.com/app?access_token=%7Byour_access_token%7D return a valid JSON
And the Facebook Tool AccessToken https://developers.facebook.com/tools/debug/accesstoken return a still valid TOKEN
What changed from user point of view:
Now, When I click on the FacebookLoginButton, I have a new dialog that ask "connection as %FacebookUserName", with 2 buttons ("Unconnect" & "Cancel")
I posted a bug report at Firebase, but I even do not know if this is Facebook or Firebase, any help, advise for exploring new issue surface or solution is welcome.
In Facebook Developper Console, switch-off the option about the "app key that is integrated in the client".
For me this changed the behavior. I will give more information as far I get from Firebase/Facebook
Here is a French Screenshot to help you setting up Facebook:
I'm working a project to integrate Stripe's payment service to my android app. I have the basic client code setup.
Card card = new Card("4242424242424242", 12, 2016, "123");
boolean validate = card.validateCard();
if (validate) {
try {
new Stripe(TEST_PUBLUSHABLE_KEY).createToken(card, new TokenCallback() {
#Override
public void onError(Exception e) {
System.out.println("ERROR");
}
#Override
public void onSuccess(Token token) {
System.out.println("SUCCESS");
}
});
} catch (AuthenticationException e) {
e.printStackTrace();
}
}
Now I need to setup a server, which I plan on using Node.js and Express. I followed their sample code on: https://stripe.com/docs/tutorials/charges
var express = require('express');
var bodyParser = require('body-parser');
var stripe = require('stripe')('sk_test_sGyqMsiFmf45xoZrDCy5ItcU'); // Test Secret Key
var app = express();
app.use(bodyParser());
app.post('/charge', function(req, res) {
var stripeToken = request.body.stripeToken;
var charge = stripe.charges.create({
amount: 1000, // amount in cents, again
currency: "cad",
card: stripeToken,
description: "payinguser#example.com"
},
function(err, charge) {
if (err && err.type === 'StripeCardError') {
console.log("The card has been declined");
}
});
});
app.use(express.static(__dirname + '/public'));
app.listen(3000);
I have never worked with servers, so I think I'm having trouble communicating between the android app and the server that is on my computer's localhost:3000.
According to Stripe's documentation, I need to have my server accept a HTTP POST call for the token, but I'm not quite sure how to do that.
Really appreciate your help.
Update #1:
Use Ultrahook to forward Stripe's POST to my localhost.
I use Node.js to setup my server, which receives all Stripe's requests and then get the information I need from the request body.
Still having trouble getting the onSuccess callback on Android to work, it always go to the onError callback.
Update #2:
Solved the onError callback error by printing the error message to console.
Permission denied (missing INTERNET permission?
Turns out I need to include this line to the AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
Link to the Stack Overflow post that solved this part of my problem:
What permission do I need to access Internet from an android application?
I'm trying to make my Android Application to log into my WebAPI services. I want to share my ideas in order to verify them.
If the access to WebAPI is performed via WebSite the steps are:
1- Call WebAPI method for logging in
2- WebAPI redirect client to facebook
3- Facebook login and returns a Token
4- If I use that token in the next calls I'll be authenticated as the right user.
And this works.
If the access to WebAPI is performed via Android APP, how can I get the access token?
Actually I'm doing something like:
1- Contact Facebook via Login Button
2- Getting logged id to Facebook receiving a Token
3- Trying to perform WebAPI calls adding the Authentication: Bearer CODE to my calls
At that point I'm wandering..
How can my application now that I'm THAT particular user? If I perform something like
GET /API/METHOD1
Authentication: Bearer CODE
How can it knows that the CODE is me if the Android Application never told him? Does the application automatically contact Facebook in order to receive an answer like "yeah! I release that token, it is related to..."
Or I'm misunderstanding everything?
The other way I can figure it out is that I must use an "hybrid approach" like:
1- Call WebAPI (as via browser)
2- Get Redirect link to Facebook
3- Get the token
But.. At that point, how can I swith between Facebook App / Facebook Site to my Android application again?
Sorry for the mess, I'm trying to find out the logic beside this auth process.
Ok, I think I've got it!
when WebAPI receives the Facebook Token doesn't know anything about user and authorizations. BUT, due to the token, can access to Facebook "as the caller".
By this way the application could perform something like:
Android -> Facebook Login -> Get FBToken
Android -> Web API -> Send FBToken
Web API -> Facebook -> /me Sending FBToken
Facebook -> Web API -> Identity
Web API -> Andoid -> This is the token for you Identity
Android -> Web API -> Give me Auth Method, Authorization: Bearer WebAPIToken
I found out a useful class online: (based on WebApi ASP.NET Identity Facebook login)
private async Task<FacebookUserViewModel> VerifyFacebookAccessToken(string accessToken)
{
FacebookUserViewModel fbUser = null;
var path = "https://graph.facebook.com/me?access_token=" + accessToken;
var client = new HttpClient();
var uri = new Uri(path);
var response = await client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
fbUser = Newtonsoft.Json.JsonConvert.DeserializeObject<FacebookUserViewModel> (content);
}
return fbUser;
}
public class FacebookUserViewModel
{
[JsonProperty("id")]
public string ID { get; set; }
[JsonProperty("first_name")]
public string FirstName { get; set; }
[JsonProperty("last_name")]
public string LastName { get; set; }
[JsonProperty("username")]
public string Username { get; set; }
[JsonProperty("email")]
public string Email { get; set; }
}
So, you could have a WebAPI like:
[HttpGet]
[AllowAnonymous]
public async Task<string> GetTokenFromFacebook(string accessToken)
{
var user = await VerifyFacebookAccessToken(accessToken);
//Search on your DB for the user ID or email
//Get token for user
return token;
}
A complete and perfect explanation is here:
http://thewayofcode.wordpress.com/2014/03/01/asp-net-webapi-identity-system-how-to-login-with-facebook-access-token/
Example of token creation
var tokenExpirationTimeSpan = TimeSpan.FromDays(14);
var identity = new ClaimsIdentity(Startup.OAuthBearerOptions.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, UserName, null, "Facebook"));
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.ToString(), null, "LOCAL_AUTHORITY"));
AuthenticationTicket ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
var currentUtc = new Microsoft.Owin.Infrastructure.SystemClock().UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(tokenExpirationTimeSpan);
var accesstoken = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
I hope it helps!