Ruby 2.2.4
Rails 5.0.0.1
retrofit:1.9.0'
otto:1.3.8'
Hello People,
I am just a beginner in programming and I have the task to create an android app with Android Studio, where you can login and logout as a user. Therefore
I programmed a Token-based authentication in RoR 5 API with Json Web Token (JWT). This tutorial helped me: http://tutorials.pluralsight.com/ruby-ruby-on-rails/token-based-authentication-with-ruby-on-rails-5-api ). After putting the right credentials in curl I finally receive the token: {"auth_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE0NjA2NTgxODZ9.xsSwcPC22IR71OBv6bU_OGCSyfE89DvEzWfDU0iybMA"}
Now after this works fine I tried to implemend the login-function in android studio, which should interact with my RoR server.
I followed strictly a tutorial (https://www.sitepoint.com/retrofit-a-simple-http-client-for-android-and-java/ )which uses Retrofit for authentication with a php server. However I want to implement this for my RoR server. Nevertheless I tried this code out. I was happy, that the program can communicate with my ruby on rails server. If I login on android app with the right credentials, I see on the console that it will be accepted.
However I dont know how I get a message on android, that the login was succesful plus a welcome message with the name of the user. As I said I use JWT in RoR which gives a long token back. Is there a possibility that retrofit can interpret this token? Or do I need to install JSON Web Token for android? Here is a part of the code in ruby on rails:
app/commands/authenticate_user.rb
class AuthenticateUser
prepend SimpleCommand
def initialize(email, password)
#email = email
#password = password
end
def call
JsonWebToken.encode(user_id: user.id) if user
end
private
attr_accessor :email, :password
def user
user = User.find_by_email(email)
return user if user && user.authenticate(password)
errors.add :user_authentication, 'invalid credentials'
nil
end
end
lib/json_web_token.rb
class JsonWebToken
class << self
def encode(payload, exp = 24.hours.from_now)
payload[:exp] = exp.to_i
JWT.encode(payload, Rails.application.secrets.secret_key_base)
end
def decode(token)
body = JWT.decode(token, Rails.application.secrets.secret_key_base)[0]
HashWithIndifferentAccess.new body
rescue
nil
end
end
end
And here comes a part of my android app, which code is from the tutorial I followed:
public class Communicator {
private static final String TAG = "Communicator";
private static final String SERVER_URL = "http://127.0.0.1/retrofit";
public void loginPost(String username, String password){
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(SERVER_URL)
.setLogLevel(RestAdapter.LogLevel.FULL)
.build();
Interface communicatorInterface = restAdapter.create(Interface.class);
Callback<ServerResponse> callback = new Callback<ServerResponse>() {
#Override
public void success(ServerResponse serverResponse, Response response2) {
if(serverResponse.getResponseCode() == 0){
BusProvider.getInstance().post(produceServerEvent(serverResponse));
}else{
BusProvider.getInstance().post(produceErrorEvent(serverResponse.getResponseCode(), serverResponse.getMessage()));
}
}
#Override
public void failure(RetrofitError error) {
if(error != null ){
Log.e(TAG, error.getMessage());
error.printStackTrace();
}
BusProvider.getInstance().post(produceErrorEvent(-200,error.getMessage()));
}
};
communicatorInterface.postData("login", username, password, callback);
}
public void loginGet(String username, String password){
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(SERVER_URL)
.setLogLevel(RestAdapter.LogLevel.FULL)
.build();
Interface communicatorInterface = restAdapter.create(Interface.class);
Callback<ServerResponse> callback = new Callback<ServerResponse>() {
#Override
public void success(ServerResponse serverResponse, Response response2) {
if(serverResponse.getResponseCode() == 0){
BusProvider.getInstance().post(produceServerEvent(serverResponse));
}else{
BusProvider.getInstance().post(produceErrorEvent(serverResponse.getResponseCode(), serverResponse.getMessage()));
}
}
#Override
public void failure(RetrofitError error) {
if(error != null ){
Log.e(TAG, error.getMessage());
error.printStackTrace();
}
BusProvider.getInstance().post(produceErrorEvent(-200,error.getMessage()));
}
};
communicatorInterface.getData("login", username, password, callback);
}
}
This is the Php script from the android tutorial. Maybe I need to transfer this into ruby
<?php
//Post Method here
if(isset($_POST['method']) == 'login'){
$username = $_POST['username'];
$password = $_POST['password'];
if($username == "admin" && $password == "admin"){
$response = array('returned_username' => "-admin-",
'returned_password' => "-admin-",
'message' => "Your credentials are so weak [USING_POST]!",
'response_code' => "1");
echo json_encode($response);
}else{
$response = array('response_code' => "-1",
'message' => "invalid username or password");
echo json_encode($response);
}
}
The json encoding in php includes an array (username, password, message, responsecode). How can I include the same array with JWT on ruby on rails? Any idea? And how I get a message on android, that the login was succesful
In the line JsonWebToken.encode(user_id: user.id) if user, you're passing a hash with a key user_id. You could extend this line to include additional attributes, somewhat like
JsonWebToken.encode(user_id: user.id, name: user.name) if user.
You will then need to include a JWT library in your android app and decode the server response (obtained from Retrofit) and you'll be able to access the attributes you encoded.
There is one way to retrieve the token value, first add those lines to your ServerResponse.java file:
#SerializedName("token")
private String token;
String token
this.token = token;
public String getToken() { return token; }
public void setToken(String token) {
this.token = token;
}
According to what is already there and then go to then main activity and store the token in a variable such as this:
String auth_token = serverEvent.getServerResponse().getToken()
next you can make it private final and use it all over your project.
Hope it helps!
Related
I am currently testing Microsoft Azure and the App Service/Mobile Apps feature using a native Android app and C# on the back end.
I started with the Getting Started application (ToDo) as the base app and now I am trying to enable Authentication using the https://azure.microsoft.com/en-us/documentation/articles/app-service-authentication-overview/ page and Google as the provider.
So far I have
created a Google project with a OAuth Web client
the authorized redirect uri set there is: https://.azurewebsites.net/.auth/login/google/callback
in the Azure portal and the App Service instance I have enabled Authorization/Authentication
the "Action to take when request is not authenticated" option is set to "Allow Request"
For the Google Provider I have set the Client Id and Client Secret
In the Android app I am using the GoogleApiClient class to let the user select a Google Account. Also I get the ID token and the Server Auth Code
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.requestIdToken(getString(R.string.server_client_id))
.requestServerAuthCode(getString(R.string.server_client_id))
.build();
mScopes = gso.getScopeArray();
mGoogleApiClient = new GoogleApiClient.Builder(this)
.enableAutoManage(this, this)
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.build();
Once the user picks up an account I retrieve the token and code and then I ask for an access token using the GoogleAuthUtil class. After I get the access_token I try to exchange it with an App Service token (authenticate2 method)
private void handleSignInResult(GoogleSignInResult result) {
Log.d("", "handleSignInResult: " + result.isSuccess());
if(result.isSuccess()) {
final GoogleSignInAccount account = result.getSignInAccount();
final String idToken = account.getIdToken();
String serverAuthCode = account.getServerAuthCode();
mSignInButton.setVisibility(View.GONE);
mGoogleUserText.setText(account.getDisplayName());
mGoogleUserText.setVisibility(View.VISIBLE);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.edit().putString("idToken", idToken).commit();
prefs.edit().putString("serverAuthCode", serverAuthCode).commit();
new AsyncTask<Void, Void, String>() {
#Override
protected String doInBackground(Void... params) {
try {
StringBuilder scopesBuilder = new StringBuilder("oauth2:");
for(Scope scope : mScopes) {
scopesBuilder//.append("https://www.googleapis.com/auth/")
.append(scope.toString())
.append(" ");
}
String token = GoogleAuthUtil.getToken(ToDoActivity.this,
account.getEmail(), scopesBuilder.toString());
return token;
} catch (IOException | GoogleAuthException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(String result) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ToDoActivity.this);
prefs.edit().putString("accessToken", result).apply();
authenticate2();
}
}.execute();
} else {
mSignInButton.setVisibility(View.VISIBLE);
mGoogleUserText.setVisibility(View.GONE);
}
}
private void authenticate2() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String idToken = prefs.getString("idToken", null);
String serverAuthCode = prefs.getString("serverAuthCode", null);
String accessToken = prefs.getString("accessToken", null);
JsonObject json = new JsonObject();
json.addProperty("access_token", accessToken);
json.addProperty("id_token", idToken);
json.addProperty("authorization_code", serverAuthCode);
ListenableFuture<MobileServiceUser> loginFuture =
mClient.login(MobileServiceAuthenticationProvider.Google, json);
Futures.addCallback(loginFuture, new FutureCallback<MobileServiceUser>() {
#Override
public void onSuccess(MobileServiceUser result) {
createTable();
}
#Override
public void onFailure(Throwable t) {
Log.e(TAG, t.getMessage(), t);
}
});
}
So I am using the MobileServiceClient.login() method to send back to the server the access_token of the user in order to get back an Azure session.
Nevertheless, this call fails and I get back a MobileServiceException:
com.microsoft.windowsazure.mobileservices.MobileServiceException: You do not have permission to view this directory or page.
Any ideas what am I missing here?
Thanks
Well this is more than embarassing :-)
I first tried to "manually" verify the id_token using the Google tokeninfo endpoint:
https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=
but I was getting a generic error that didn't give a lot of info.
Then I used the Google API Java client library and created a small test to verify my token (more info here: https://developers.google.com/identity/sign-in/android/backend-auth)
That test was failing as well and I realized that the reason for that was the expiring time of my token which was smaller than the current time. And this was happening because my emulator time was not correct!
When I set the "correct" timezone everything worked as expected.
Sorry for the post guys. You can use the code here as a template and don't forget to check your emulator time :-)
I want to make a diet helper app for android devices, using android studio and
I need ideas on what to use to implement the login/register system, I followed a tutorial on youtube but it was outdated and I ended up wasting my time, then I've read on google, that android studio has a library called volley that I can use with PHP and MySql to make the login system.
Do you have other ideas, or is that the best one to go with?
I'm open to suggestions so shoot!
Update:
I've created a post about how to do this using a PHP backend for your Android application. https://keithweaver.ca/posts/4/android-php-custom-login
Additionally to the link above, this is how you can setup a server.
https://github.com/kweaver00/tutorials/blob/master/setup-server.md
https://keithweaver.ca/posts/9/setup-ubuntu-server-quickly
Original Post:
This is one solution and isn't guaranteed to be the best.
You can really use anything to communicate with a server. Async Tasks or Retrofit are both popular.
Assuming you have set up a server with a LAMP stack. Make sure you have an SSL so you don't pass user information that isn't encrypted.
Create a user table in mysql
Ex.
id int default->NULL AI primary-key
user varchar 250 default->null
pass varchar 250 default->null
signupdate date default-> null
Create a log in sessions table of some sort
Ex.
id int default->NULL AI primary-key
user varchar 250 default->null
token varchar 250 default->null
addedDate date default->null
Create a log in php script (I know this probably isnt the best way to right php code)
$connection = mysqli_connect("localhost", "phpmysqluser", "password", "dbname") or die("Error 404: unable to connect");
$username = $_POST['user'];
$pass = $_POST['pass'];
//add code to remove slashes and etc.
$result = mysqli_query($connection, "SELECT * FROM userTable WHERE user='$username' AND pass='$pass'") or die("Error: this line has error");
class response{
public $loggedin =0;
public $message = "";
}
$response = new response();
if(mysqli_num_rows($result) == 1){
$logInToken = generateLogInToken();
//have a function that creates a unique token and stores it for X days or minutes
$response->loggedin = 1;
$response->message = $logInToken;
}else{
$response->message = "wrong info";
}
echo json_decode($response);
This should output a json file like this depending on your user and pass variables.
{
"loggedin" : 1,
"message" : "asdnlansdkansd"
}
Right another script that passes in the log in token and user name to check if it's valid.
$connection .... //same as above
//well it really should be a include_once cause if you change credentials
$token = $_POST['token'];
$user = $_POST['user'];
$registeredDate = "";
$today = date('Y-m-d');
$result = mysqli_query($connection, "SELECT * FROM tokenTable WHERE user='$user' AND token='$token'") or die("Error...");
class response{
public $status = 0;
}
$response = new response();
if(mysqli_num_rows($result) == 1){
//check token has been register today and if not sign them out
while($row = mysqli_fetch_array($result)){
$registeredDate = $row['addedDate'];
}
if($registeredDate == $today){
//token is valid
$response->status = 3;
}else{
//expired
$response->status = 2;
}
}else{
//user and token are not valid
$response->status = 1;
}
echo json_decode($response);
Giving a json object like:
{
"status" : 3
}
In your Android app on open, run the code to check if the account is valid if there is anything stored locally. Or just go to log in screen.
On splash screen in the onCreate (you dont need a splash screen, its actually not recommended but its the easiest way to explain the process):
if(userNameAndTokenStoredInSharedPref()){
String token = getTokenFromSharedPref();
String userName = getUserNameFromSharedPref();
checkAgainstServer(token, userName);
}else{
Intent openLogInWindow = new Intent(this, LogInActivity.class);
startActivity(openLogInWindow);
}
still in the slash activity but out of the oncreate:
protected void checkAgainstServer(String token, String user){
//using retrofit
ThisAppRestClient.get().postCheckTokenAndUser(token, user, new Callback<UserStatusCallBack>() {
#Override
public void success(UserStatusCallBack userStatusCallback, retrofit.client.Response response) {
if(userStatusCallback.getStatus() == 1){
//Invalid token
}else if(userStatusCallback.getStatus() == 2){
//Expired token
}else if(userStatusCallback.getStatus() == 3){
//Success
Intent openMainWindow = new Intent(this, MainActivity.class);
startActivity(openMainWindow);
}
}
#Override
public void failure(RetrofitError error) {
//Retrofit errors like timeouts, etc.
}
}
}
The log in activity would be something like:
logBtn.setOnClickListener(new View.onClick...
String userName = userNameEditText.getText().toString().toLowerCase().trim();
String password = passwordEditText.getText().toString().trim();
if(!TextUtils.isEmpty(userName) && !TextUtils.isEmpty(password)){
callServerLogInScript(userName, password);
}
userNameEditText.setText("");
logBtn.setVisibility(View.GONE);
}
lower down the file:
protected void callServerLogInScript(String user, String pass){
//using retrofit
ThisAppRestClient.get().postCheckTokenAndUser(user, pass, new Callback<LogInCallBack>() {
#Override
public void success(LogInCallBack logInCallback, retrofit.client.Response response) {
if(logInCallback.getLoggedIn() == 1){
//succssful
storeUserNameInSharedPref(user);
storeTokenInSharedPref(logInCallback.getMessage());
Intent openMainActivity = new Intent(this, MainActivity.class);
startActivity(openMainActivity);
}else{
//incorrect log in
logBtn.setVisibility(View.VISIBLE);
}
}
#Override
public void failure(RetrofitError error) {
//Retrofit errors like timeouts, etc.
}
}
}
The reason for not storing the user name and password directly is if the device is rooted they can manipulate the data locally but not on your server.
It depends which you want to use. If you have your own server to host, then use php,mysql. If not, you can also use other third party which provides you to add if you know php,mysql to create.
Another option is if you don't want to use php mysql to store datas, then you can proceed with parse.com
So if you want to use parse.com, just register it. It's free to use.
Hope it will match your requirement, say for eg: if you want to create registration(everything saving in datas will be handled),you need to give exact object name that matches what you given in parse.com
Even you can also create in code itself without object name. I will show you a piece of example how to create and insert for registration..
ParseUser user = new ParseUser();
user.setEmail((txtEmail));//create an edittext and get the values in strings and store..
user.setPassword(txtPassword);//same for password
user.setUsername(txtUsername);//username
user.signUpInBackground(new SignUpCallback() {
public void done(ParseException e) {
if (e == null) {
//completed..it has been registered
Toast.makeText(getApplicationContext(),
"Successfully Signed up, please log in.",
Toast.LENGTH_LONG).show();
finish();
} else {
Toast.makeText(getApplicationContext(),
"Sign up Error", Toast.LENGTH_LONG)
.show();
}
}
});
Simple one if you don't want to use php,mysql. Well documentation and easy to integrate and use it. Happy coding.
FYI: Android studio is IDE for development. And volley is HTTP library that makes networking for Android.
I'm trying to use Retrofit with Restful WebService. Everything seems alright, but somehow when I run this code this will always returns this
Method not found. Retrofit 404 Error
Here is my WebServices Code
public function processApi() {
$func = strtolower(trim(str_replace("/","",$_POST['request'])));
if ((int)method_exists($this,$func) > 0) {
$this->$func();
} else {
// If the method not exist with in this class, response would be "Page not found".
$this->response('Method not found',404);
}
}
private function login() {
// Cross validation if the request method is POST else it will return "Not Acceptable" status
if ($this->get_request_method() != "POST") {
// If invalid inputs "Bad Request" status message and reason
$error = array('status' => "0", "msg" => "Bad Request");
$this->response($this->json($error), 406);
}
// Input validations
if (empty($email) and empty($password)) {
$error = array('status' => "0", "msg" => "Invalid Email address or Password");
$this->response($this->json($error), 400);
}
}
public class ObjectPost {
#SerializedName("request")
String request;
#SerializedName("email")
String event_id;
public void setRequest(String request) {
this.request = request;
}
public void setEvent_id(String event_id) {
this.event_id = event_id;
}
}
And here is my Android Request Code
public class RestClient {
public interface ClientInterface {
#POST(Config.LOGIN_URL)
void login(#Body ObjectPost mObject,
Callback<LoginBeans> callback);
}
public static ClientInterface initRestAdapter() {
OkHttpClient client = new OkHttpClient();
return (ClientInterface) new RestAdapter.Builder()
.setLogLevel(RestAdapter.LogLevel.FULL)
.setClient(new OkClient(client))
.setEndpoint(Config.SERVER_URL)
.build()
.create(ClientInterface.class);
}
}
The value in the
Config.LOGIN_URL
Is most likely incorrect. Please remember that
Config.SERVER_URL
Must contain the base URL address. e.g. http://www.server.com/ (also please note the slashes are important)
Next, what is in your attribute must be only the remainder of the specific method that would be appended on that base url. e.g. if the method you want to call is login, it should be
#POST("/login")
Once again, I am not kidding about the slashes.
Also remember that if a query parameter is sent through as null, retrofit ignores it (you may face this problem later).
If you need any further help, you already have your loglevel set to full, please add the logcat to your question so we can see what is happening.
Your code looks alright.
Basically you need to make sure your backend. Ensure that the controller or whatever is actually right.
Maybe this will be quite applicable to you https://github.com/square/retrofit/issues/789
So, instead of looking at your Android code, it's a good reason to look somewhere else I would say.
I'm developing an Android app and a RESTful API with Laravel 5 Framework.
I've got a trouble with the login activity: the flow is that the user ask a 8th characters code and the server web sends him a SMS with it. Then user can do the login using this code like a password.
This is the code that asks a code:
private void askCode(String mobile) {
GsonRequest<String> jsObjRequest = new GsonRequest<String>(
Request.Method.GET,
WebAPIRoute.authGetCode + "/" + mobile,
String.class, null,
new Response.Listener<String>() {
#Override
public void onResponse(String code) {
txtResponse.setText("Code asked successfully.");
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError volleyError) {
Toast.makeText(getBaseContext(), volleyError.getMessage(), Toast.LENGTH_SHORT).show();
}
});
this.requestQueue.add(jsObjRequest);
}
And this the method in the RESTful API to generate a code:
public function getCode($mobileNum)
{
//genero un numero casuale da mandare con l'sms
$code = mt_rand(10000000, 99999999);
Session::put('code', $code);
sendCode($mobileNum, $code); //send code by SMS
return response()->json(array("success"=>true));
}
The code generated is stored into Laravel's Session (configurated with file driver).
When the user wants to login, the app call this method:
private void saveUser(final String code, final String mobile, final String name) {
HashMap<String, String> params = new HashMap<String, String>();
params.put("nickname", name);
params.put("mobile", mobile);
params.put("code", code);
GsonRequest<String> jsObjRequest = new GsonRequest<String>(
Request.Method.POST,
WebAPIRoute.authValidateCode,
String.class,
params,
new Response.Listener<String>() {
#Override
public void onResponse(String authtoken) {
final Account account = new Account(accountName, mAccountType);
String authtokenType = mAuthTokenType;
// Creating the account on the device and setting the auth token we got
// (Not setting the auth token will cause another call to the server to authenticate the user)
mAccountManager.addAccountExplicitly(account, code, null);
mAccountManager.setAuthToken(account, authtokenType, authtoken);
Bundle data = new Bundle();
data.putString(AccountManager.KEY_ACCOUNT_NAME, accountName);
data.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccountType);
data.putString(AccountManager.KEY_AUTHTOKEN, authtoken);
data.putString(PARAM_USER_PASS, code);
data.putBoolean(ARG_IS_ADDING_NEW_ACCOUNT, true);
final Intent res = new Intent();
res.putExtras(data);
setAccountAuthenticatorResult(res.getExtras());
Intent i = new Intent(getBaseContext(), MyEventsActivity.class);
startActivity(i);
}
}
,
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError volleyError) {
Log.e(TAG, volleyError.getMessage(), volleyError);
showMessage("Errore nell'autenticazione. Riprova piu` tardi.");
}
});
requestQueue.add(jsObjRequest);
}
The method of API that validate the code is this:
public function validateCode() {
$code = trim(Input::get('code'));
$nickName = trim(Input::get('nickname'));
$phoneNum = trim(Input::get('mobile'));
if (empty($phoneNum))
abort(400, 'mobile parameters not provided.');
if (empty($code))
abort(400, 'code parameters not provided.');
if (empty($nickName))
abort(400, 'nickname parameters not provided.');
$validCode = Session::get('code');
Log::info('code: ' . $code . " validCode: " . $validCode);
if($code == $validCode) {
Session::forget('code');
// Retrieve the user by the attributes, or instantiate a new instance...
$user = User::firstOrCreate(['Mobile' => $phoneNum]);
//aggiorno i campi nickname e password col nuovo codice
$user->Nickname = $nickName;
$user->password = $code;
//save!
$user->save();
//viene usata l'autenticazione di Laravel e gli serve una password
$token = JWTAuth::attempt(['Mobile' => $phoneNum, 'password' => $code]);
return response()->json($token);
} else {
//return response()->json(array('success' => false, 'error' => 'The code isn\'t correct.'));
abort(401, 'The code isn\'t correct.' . $validCode);
}
}
I've tested the RESTful API with Chrome and Firefox and the login works. With the app no. Infact the issue is that Session::get('code'); in validateCode returns always an empty value. I checked the Session file generated with Session::put('code', $code); and is correct. But when Session::get('code') is called, Laravel generates another Session file and seems not use the previous.
I disabled the CSRF Middleware in RESTful API.
What is wrong?
Storing the session on the server side is pointless. API's are suppose to be stateless so the second you finish the first request for the code and store it in a session on the server side, the session will end and the next request will not remember anything you set.
If you wanted to keep the code login and avoid using tokens, then you will have to send a unique code from the android app which identifies the user. Then generate the code on the server side and store it in a table with user_identifier and generated_code and create a model to access it e.g.
AttemptedLogin
user_id | generatedCode
0001 | 87392042
0032 | 83214320
Then on add this to saveUser
params.put("user_id", user_id); // can be the android device ID or even a unique timestamp
Finally on the server side in validateCode replace $validCode line with the following:
$user_id = trim(Input::get('user_id'));
....
$validCode = AttemptedLogin::where('user_id', $user_id)->first();
if($code == $validCode->generatedCode) {
$validCode->delete();
....
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!