I am using Slim framework to return JSON to my Android device. I am currently working on login on my device. I am using 3 different ways to login: Facebook, Google and account login. When he takes account login he can register a new account or login with an existing one.
For security on my web service I thought to use JWT security. So I am reading and watching video's about how it works. I think I understand how it works, but I cannot find anything about how to implement it correctly.
The middleware I use for slim v3 is called: Slim-JWT-Auth.
I found the following link to implement this in my slim framework, and it works correctly I think.
Now my questions:
How do I generate my Token?
When do I generate my Token?
Do I also need a Token when using Google or Facebook sign-in? because they already use a Auth2.0 token?
I understand how it works but nobody is talking about when and how to implement it. So when do I need to generate the token (on login on the webservice?), and do I need to generate a token after every start of the app, or do I just need to wait until the token expires?
How do I generate my Token?
Since the middleware already includes firebase/php-jwt library you can use it to generate the token.
$now = new DateTime();
$future = new DateTime("now +2 hours");
$server = $request->getServerParams();
$payload = [
"iat" => $now->getTimeStamp(),
"exp" => $future->getTimeStamp(),
"sub" => $server["PHP_AUTH_USER"]
];
$secret = "supersecretkeyyoushouldnotcommittogithub";
$token = JWT::encode($payload, $secret, "HS256");
When do I generate my Token?
In your api you can for example include a password protected route which returns the token. All other routes except /token are JWT authenticated. Client can request token with every request or just always bit before the old one expires.
$app->add(new \Slim\Middleware\HttpBasicAuthentication([
"path" => "/token",
"users" => [
"test" => "test"
]
]);
$app->add(new \Slim\Middleware\JwtAuthentication([
"secret" => "supersecretkeyyoushouldnotcommittogithub"
"rules" => [
new RequestPathRule([
"path" => "/",
"passthrough" => ["/token"]
])
]
]);
$app->post("/token", function ($request, $response, $arguments) {
$now = new DateTime();
$future = new DateTime("now +2 hours");
$server = $request->getServerParams();
$payload = [
"iat" => $now->getTimeStamp(),
"exp" => $future->getTimeStamp(),
"sub" => $server["PHP_AUTH_USER"],
];
$secret = "supersecretkeyyoushouldnotcommittogithub";
$token = JWT::encode($payload, $secret, "HS256");
$data["status"] = "ok";
$data["token"] = $token;
return $response->withStatus(201)
->withHeader("Content-Type", "application/json")
->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
});
Do I also need a Token when using Google or Facebook sign-in? because they already use a Auth2.0 token?
There is no clear answer to this. It "depends". You could for example authenticate your /token route with Facebook or Google and return your own JWT token from there.
There is an work in progress more detailed example implementation of everything above you might want to check.
Related
I want to use a Firestore database and Datastore in the same Android app. I know that it's not possible to use both in the same GCP project(explained here: Firestore and Datastore in the same GAE project) so I created two different projects, one for the Firestore and one for the Datastore. So now the problem is that I can't create a client Id for my Android app in both projects because of "duplicated fingerprints".
If I had just one project I normally would just login via Firebase AuthUi and then use the token from the FirebaseUser to build my service handler.
//login via FirebaseUI
FirebaseAuth auth = FirebaseAuth.getInstance();
AuthUI.getInstance().createSignInIntentBuilder()
.setTheme(getSelectedTheme())
.setLogo(getSelectedLogo())
.setAvailableProviders(getSelectedProviders())
.setTosAndPrivacyPolicyUrls(getSelectedTosUrl(),getSelectedPrivacyPolicyUrl())
.setIsSmartLockEnabled(true,true)
.build(),
FirebaseUser firebaseUser = FirebaseAuth.getInstance().getCurrentUser();
Task<GetTokenResult> tokenResultTask = firebaseUser.getIdToken(false);
tokenResultTask.addOnCompleteListener(task -> {
String result = task.getResult().getToken();
//Creating the service object
GoogleCredential credential = new GoogleCredential();
credential.setAccessToken(result);
EndpointsApi.Builder builder = new EndpointsApi.Builder(
AppConstants.HTTP_TRANSPORT,
AppConstants.JSON_FACTORY, credential);
builder.setApplicationName("endpointsapi-server");
return builder.build();
});
This works very well if I only have one project. But what to do if there are mutliple? The google-services.json is associated with the one project that also has the android client id, but the second project can't create an android client id using the same sha1 and package name(duplicated fingerprints).
If I just use the access token from the first project to build the service object(which belongs to the second project) I just get an error:
401 Unauthorized
{
"code": 401,
"errors": [
{
"domain": "global",
"message": "Authorization required",
"reason": "required"
}
],
"message": "Authorization required"
}
which makes sense, because the second project doesn't know about the first one. Im lost on what to do to somehow use both at the same time.
In my web app I just whitelist the web client of the first project in the second one and then use the credentials of the first, obtained using firebase.AuthUi, to also login the second one. Which works.
this.uiConfig = {
callbacks: {
signInSuccess: (currentUser, credential, redirectUrl) => {
const googleAuthcredential =
firebase.auth.GoogleAuthProvider.credential(credential['idToken']);
firebase.apps[1].auth().signInAndRetrieveDataWithCredential(googleAuthcredential):
}
}
};
this.ui = new firebaseui.auth.AuthUI(firebase.auth());
this.ui.start('#firebaseui-auth-container', this.uiConfig);
How to achieve the same thing in Android?
Thank you very much in advance.
I have fully integrated Firebase Auth in my Android App, now I want the client to interact with my backend (rails) using a unique Token. my question is this how it's done :
User is logged-in, using for example Facebook Auth,
Send the Firebase Token to backend and verify it
Respond to the client with a unique Token for the upcoming Api requests (get users data, save user data ...)
Thank you
I have developed the firebase_id_token gem.
It deals with all the certificates/signature business.
It can be used simply as:
FirebaseIdToken::Signature.verify(token)
Taking JinLiu's answer forward , once you get the Token in your android code , send it to your backend say : https://www.yourbackend.com?authenticate?token=asdad7687h...
Note that the token generated by Firebase is a JWT token , we need to first verify its authenticity and decode it in the backend. For this , you need to add these two gems to your gemfile
gem 'jwt' , '1.5.6'
gem 'rest-client' , '2.0.1'
With that done , you need to add these two (private) function in your controller:
def get_set_user
begin
token = params[:token]
if token.nil?
#user = nil
return
end
firebase_id = verify_user(token)[0]["user_id"]
if User.where(:firebase_id => firebase_id).exists?
#user = User.where(:firebase_id => firebase_id).first
else
#user = User.new(:firebase_id => firebase_id)
#user.save
#user
end
rescue
#user = nil
end
end
def verify_user(token)
certificate_url = "https://www.googleapis.com/robot/v1/metadata/x509/securetoken#system.gserviceaccount.com"
myresponse = RestClient.get(certificate_url).body
certificates = JSON.parse myresponse.gsub('=>', ':')
myjson =""
certificates.each do|key , value|
begin
x509 = OpenSSL::X509::Certificate.new(value)
iss = 'https://securetoken.google.com/<yourdomain>'
aud = 'yourdomain' # change this
myjson = JWT.decode(token, x509.public_key, true,
{ algorithm: "RS256", verify_iat: true ,
iss: iss , verify_iss: true ,
aud: aud , verify_aud: true
})
return myjson
rescue
end
end
return nil
end
Now you can call get_set_user as before action to see , if the user has valid token .
The idea is simple. Check if the token is signed by one of the keys mentioned at https://www.googleapis.com/robot/v1/metadata/x509/securetoken#system.gserviceaccount.com . If yes , decode the token and get the firebase id .
You do not need step #3. Firebase Auth Android SDK getToken() API returns a short-lived (one hour) Firebase Auth ID Token for the login user, and automatically returns a new token if the previous one expires.
The Firebase token verification doc describes how to get a Firebase token on client app and verify the token on server side.
I am trying to generate server auth code in android
gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.requestScopes(new Scope(Scopes.DRIVE_APPFOLDER))
.requestServerAuthCode(getString(R.string.server_client_id), false)
.build();
Then I have tried to get server auth code like this.
result.requestServerAuthCode(getString(R.string.server_client_id), false)
Suppose i got a auth token like 'bla bla bla';
Then using laravel socialite I am trying to get user on the server side
Socialize::driver('google')->userFromToken('bla bla bla')
It shows me error
GuzzleHttp\Exception\ClientException with message 'Client error: GET https://www.googleapis.com/plus/v1/people/me?prettyPrint=false resulted in a 401 Unauthorized` response:
{"error":{"errors":[{"domain":"global","reason":"authError","message":"Invalid Credentials","locationType":"header","loc (truncated...)
Actually the code sent by google on android is not the access token to get access token you can do this on laravel controller.
Install this composer library https://github.com/pulkitjalan/google-apiclient
$client = new \PulkitJalan\Google\Client(['client_id' => 'YOUR_CLIENT_ID', 'client_secret' => 'YOUR_SECRET', 'redirect_uri' => 'YOUR_REDIRECT_URI', 'developer_key' => 'YOUR_KEY']);
$google = $client->getClient();
$google->authenticate($token);
$access_token = $client->getAccessToken()["access_token"];
//and now here you go
$user = Socialize::driver('google')->userFromToken($access_token);
The easiest way is to use the Google_Client class that is provided from Google. you can find it here
Now, you've got two option:
You can send an id_token to the server, and validate it just as the link said and then just fetch the user info from the object received.
You can send the backend_auth_code to the server instead of the id_token, and then use the method fetchAccessTokenWithAuthCode($code) of the Google_Client class. This gives you the access token, id token and other stuff. You can then use that access_token with Laravel Socialite
$client = new Google_Client([
'client_id' => config('services.google.client_id'),
'client_secret' => config('services.google.client_secret')
]);
$data = $client->fetchAccessTokenWithAuthCode($code);
$user = Socialite::driver('google')->scopes(['profile','email'])->userFromToken($data['access_token']);
I am using https://github.com/Wizcorp/phonegap-facebook-plugin to connect my cordova Android app to Facebook.
I can login with:
facebookConnectPlugin.login(["email"],
fbLoginSuccess,
function (error) { alert("ERROR:" + JSON.stringify(error)); }
);
, logout even call facebookConnectPlugin.getLoginStatus and get:
userID
accessToken
expiresIn
sig
status
but when FB.api('/me', function(response){...}) is called, I receive
{error:
{
message: An active access token must be used to query information about the current user,
type: OAuthException,
code: 2500
}
}
Also this only happens when the app is built, not tested in browser.
Solved issue by manually giving FB.apicall a token with:
FB.api('/me?access_token='+userdata.authResponse.accessToken, function(response) {...
where userdata is the response of facebookConnectPlugin.getLoginStatus
I just don't understand why doesn't it provide token automatically in android app like it does in browser.
Send the access token and the data retrieving fields with the apiString
const fields = ['email', 'first_name', 'last_name', 'gender', 'birthday', 'picture.type(large)'];
const apiString = `me?access_token=${accessToken}&fields=${fields.join(',')}`;
I'm developing a Sinatra app for which I'd like to use OmniAuth. So far, I have something similar to this for the web app:
http://codebiff.com/omniauth-with-sinatra
I'd like the web app to be usable via Android phones which would use an API, authenticating by means of a token. The development of an API seems to be covered nicely here:
Sinatra - API - Authentication
What is not clear is now I might arrange the login procedure. Presumably it would be along these lines:
User selects what service to use, e.g. Twitter, FaceBook &c., by means of an in-app button on the Android device.
The Android app opens a webview to log in to the web app.
A token is somehow created, stored in the web app's database, and returned to the Android app so that it can be stored and used for subsequent API requests.
I'm not very clear on how point 3 might be managed - does anyone have any suggestions?
As no-one seems to have any suggestions, here's what I've come up with so far. I don't think it's very good, though.
I've added an API key to the user model, which is created when the user is first authenticated:
class User
include DataMapper::Resource
property :id, Serial, :key => true
property :uid, String
property :name, String
property :nickname, String
property :created_at, DateTime
property :api_key, String, :key => true
end
....
get '/auth/:name/callback' do
auth = request.env["omniauth.auth"]
user = User.first_or_create({ :uid => auth["uid"]},
{ :uid => auth["uid"],
:nickname => auth["info"]["nickname"],
:name => auth["info"]["name"],
:api_key => SecureRandom.hex(20),
:created_at => Time.now })
session[:user_id] = user.id
session[:api_key] = user.api_key
flash[:info] = "Welcome, #{user.name}"
redirect "/success/#{user.id}/#{user.api_key}"
end
If the authorisation works then the api_key is supplied to the Android app, which will presumably store it on the device somewhere:
get '/success/:id/:api_key', :check => :valid_key? do
user = User.get(params[:id],params[:api_key])
if user.api_key == params[:api_key]
{'api_key' => user.api_key}.to_json
else
error 401
end
end
All API calls are protected as in the link in my original post:
register do
def check (name)
condition do
error 401 unless send(name) == true
end
end
end
helpers do
def valid_key?
user = User.first(:api_key => params[:api_key])
if !user.nil?
return true
end
return false
end
end
For public use I'll only allow SSL connections to the server. Any suggestions for improvement would be welcome.