Use Firebase and Node.js for authenticating user on Android - android

I'm totally lost as to how to do this. I want to be able to authenticate a user with their username and password only--so I have to use a customAuth from Firebase.
I created a server (node.js) that handles the generation of tokens (runs on Heroku):
var express = require('express')
var Firebase = require('firebase')
var app = express()
app.set('port', (process.env.PORT || 5000))
app.use(express.static(__dirname + '/public'))
app.listen(app.get('port'), function() {
console.log("Node app is running at localhost:" + app.get('port'))
})
var SECRET = "numbers would be here";
var tokenGenerator = new FirebaseTokenGenerator(SECRET);
var AUTH_TOKEN = tokenGenerator.createToken({
uid: "arbitrary",
data: "blahblahblah"});
console.log(AUTH_TOKEN);
var ref = new Firebase("null");
ref.authWithCustomToken(AUTH_TOKEN, function(error, authData) {
if (error) {
console.log("Login Failed!", error);
} else {
console.log("Login Succeeded!", authData);
}
});
Now I have an Android app in which I want to authenticate a user. If I have something like,
Firebase mRef = new Firebase("myFirebaseUrl");
mRef.authWithCustomToken(String token, AuthResultHandler handler); //issue
I don't know how to get the token. Furthermore, I'm not sure I understand how it matters if the token is always the same.

You'll need to come up with a secure way to communicate the username and password from your Android client to the node.js server and to subsequently communicate the resulting token (or any error codes) back from the node.js server to the client.
While this is definitely possible (it's pretty much how Firebase email+password authentication works), it is definitely too broad a topic to cover in a StackOverflow answer. It's a project, rather than a question.
What you can consider is using Firebase email+password auth and then stubbing out the email domain. So if a user signs up with username Nxt3 and password, you simply append a dummy domain to the username and register them as Nxt3#dummydomain.com.

Related

What goes into sending a FCM from a Firestore to an Android app?

Here is what I have so far:
On Android, user logs in and makes changes to Firestore document.
Firestore document gets updated
cloud function is triggered
cloud function sends message to device(s) using device tokens
On Android, FirebaseMessagingService should receive the message but does not.
I suspect the part I am missing is device token registration. Since my server is Firebase and my users login through Firebase, do I need to take additional steps to send the device token to Firebase so that my cloud function can access it? In other words, do I store them in Firestore myself or do they come standard as part of some "users" collection that's controlled by Firebase? For more context, I adapted my cloud function from an example I found online:
CLOUD FUNCTION:
exports.coolThingIsHappening = functions.firestore.document("coolstuf/{userId}")
.onWrite(async (change, context) => {
console.log("coolThingIsHappening is triggered");
const userId = context.params.userId;
const after = change.after.data();
const payload = {
data: after
}
const tokensSnapshot = await admin.database()
.ref(`/users/${userId}/notificationTokens`).once('value');
if (!tokensSnapshot.hasChildren()) {
const logMsg = `user ${userId} has no notification tokens.`
console.log(logMsg)
return logMsg;
}
console.log("FCM tokens found")
const tokens = Object.keys(tokensSnapshot.val());
const response = await admin.messaging().sendToDevice(tokens, payload);
const tokensToRemove: Promise<void>[] = [];
console.log(`response results: ${response.results.length}`)
response.results.forEach((result, index) => {
console.log(`fcm sent: ${result.messageId}`)
const error = result.error;
if (error!.code === 'messaging/invalid-registration-token' ||
error!.code === 'messaging/registration-token-not-registered') {
tokensToRemove.push(tokensSnapshot.ref.child(tokens[index]).remove());
}
});
return Promise.all(tokensToRemove);
});
EDIT
I have proceeded to saving the fcm tokens to Firestore. Any idea how to convert the code above from database-centric to firestore-centric. I am having some trouble. Android code:
val data = mapOf("token" to it)
val collectionName = "users/${uid}/deviceTokens/"
FirebaseFirestore.getInstance().collection(collectionName).document()
.set(data)`
If you want to send messages to devices, you will need to write code to collect the device tokens in your app, and store them or send them to your backend. None of this happens automatically.
I know this is about an year since this question has been asked but it may help someone.
To send notifications to devices, you should make another collection in your firestore where you store all the tokens from users and then you can get token from there to send notification to anyone.

Firebase Auth Custom claims not propagating to client

I have a user with UID 1 where the custom claims are set as,
frompos=true
I am setting new custom claims to this user from the ADMIN SDK for java the following way:
Map<String,Object> claims = new HashMap<>();
claims.put("frompos",false);
FirebaseAuth.getInstance().setCustomUserClaimsAsync("1", claims).get(10000,
TimeUnit.MILLISECONDS);
I print the claims on the server side to check if the claims are set:
UserRecord user = FirebaseAuth.getInstance().getUserAsync("1").get(10000,
TimeUnit.MILLISECONDS);
LOG.debug("user new claims " + user.getCustomClaims());
The result as expected is that the claims get set:
user new claims {frompos=false}
Now on the android sdk side, I have the user already logged in so I am refreshing the ID token manually to propagate the claims as the docs say
(https://firebase.google.com/docs/auth/admin/custom-claims)
FirebaseAuth.getInstance().getCurrentUser().getIdToken(true).addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
#Override
public void onComplete(#NonNull Task<GetTokenResult> task) {
if(task.isSuccessful()){
Log.d("FragmentCreate","Success refreshing token "+(FirebaseAuth.getInstance().getCurrentUser()==null));
Log.d("FragmentCreate","New token "+task.getResult().getToken());
}
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.d("FragmentCreate","Failure refreshing token "+(FirebaseAuth.getInstance().getCurrentUser()==null)+" "+e.toString());
}
});
Now I use the printed Id Token printed here and verify it on server side and print the claims from it
FirebaseToken tokenTest = FirebaseAuth.getInstance(ahmedabadRepoApp).verifyIdTokenAsync(token).get(10000,TimeUnit.MILLISECONDS);
LOG.debug("Token claims are "+tokenTest.getClaims());
But the claims printed here are:
{"aud":"ahmedabadrepo","auth_time":1514724115,"email_verified":false,"exp":1514730425,"iat":1514726825,"iss":"https://securetoken.google.com/ahmedabadrepo","sub":"1","frompos":true,"user_id":"1","firebase":{"identities":{},"sign_in_provider":"custom"}}
Thus the frompos value did not propagate to the client sdk even though I did refresh the Id token manually.
I was having the same issue in angular - I set the claim using the Admin SDK on the server, but then they would not be in the user on the client.
Using the following I can see the claims in the payload:
this.firebaseAuth.auth.currentUser.getIdToken().then(idToken => {
const payload = JSON.parse(this.b64DecodeUnicode(idToken.split('.')[1]))
console.log(payload);
}
)
b64DecodeUnicode(str) {
return decodeURIComponent(atob(str).replace(/(.)/g, function (m, p) {
var code = p.charCodeAt(0).toString(16).toUpperCase();
if (code.length < 2) {
code = '0' + code;
}
return '%' + code;
}));
}
Here is a good write up of this where I copied the above:
At the moment the client-side code must parse and decode the user’s ID
token to extract the claims embedded within. In the future, the
Firebase client SDKs are likely to provide a simpler API for this use
case.
Relevant info from Firebase Docs:
Custom claims can only be retrieved through the user's ID token.
Access to these claims may be necessary to modify the client UI based
on the user's role or access level. However, backend access should
always be enforced through the ID token after validating it and
parsing its claims. Custom claims should not be sent directly to the
backend, as they can't be trusted outside of the token.
Once the latest claims have propagated to a user's ID token, you can
get these claims by retrieving the ID token first and then parsing its
payload (base64 decoded):
// https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
firebase.auth().currentUser.getIdToken()
.then((idToken) => {
// Parse the ID token.
const payload = JSON.parse(b64DecodeUnicode(idToken.split('.')[1]));
// Confirm the user is an Admin.
if (!!payload['admin']) {
showAdminUI();
}
})
.catch((error) => {
console.log(error);
This might help: https://stackoverflow.com/a/38284384/9797228
firebase.auth().currentUser.getIdToken(true)
The client sdk is caching the old token (old claims).
You should add a mechanism to refresh it after changing the claims (eg. push notification) or just wait for the old token to expires or user to lougout and login again.
It's explained here https://youtu.be/3hj_r_N0qMs?t=719
Edit
You can force the sdk to refresh the token using firebase.auth().currentUser.getIdToken(true)

Servicestack hosting on subdomain and authenticating from main domain

I am creating one web app in asp.net MVC with identity (OWIN)
framework. Now it will be hosted in one domain lets say domain.com
Now i want to host servicestack on sub domain lets say
service.domain.com
Now any user who login in domain.com with username and password and if
it success then i want to authenticate servicestack too so that all
services with [Authenticate] attribute will work.
The primary objective of hosting servicestack on subdomain is to make
code independent for database side.
And i can easily call this REST api in my future Android and iOS app.
Is it something wrong i am doing?
I have tried with code provided by mythz but now i get this error
AuthKey required to use: HS256
My MVC code is (running on: localhost:51055)
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
{
var jwtProvider = new JwtAuthProvider();
var header = JwtAuthProvider.CreateJwtHeader(jwtProvider.HashAlgorithm);
var body = JwtAuthProvider.CreateJwtPayload(new AuthUserSession
{
UserAuthId = user.Id,
DisplayName = user.NameSurname,
Email = user.Email,
IsAuthenticated = true,
},
issuer: jwtProvider.Issuer,
expireIn: jwtProvider.ExpireTokensIn,
audience: jwtProvider.Audience,
roles: new[] { "TheRole" },
permissions: new[] { "ThePermission" });
var jwtToken = JwtAuthProvider.CreateJwt(header, body, jwtProvider.GetHashAlgorithm());
var client = new JsonServiceClient("http://localhost:52893/");
client.SetTokenCookie(jwtToken);
}
}
error occured on this statement jwtProvider.GetHashAlgorithm()
Any my servicestack code is (running on: localhost:52893)
public class AppHost : AppHostBase
{
public AppHost() : base("MVC 4", typeof(HelloService).Assembly) { }
public override void Configure(Funq.Container container)
{
SetConfig(new HostConfig
{
RestrictAllCookiesToDomain = "localhost",
HandlerFactoryPath = "api",
DebugMode = true
});
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new JwtAuthProviderReader(AppSettings) {
AuthKey = AesUtils.CreateKey(),
HashAlgorithm = "RS256"
},
}));
Plugins.Add(new CorsFeature(
allowOriginWhitelist: new[] {
"http://localhost",
"http://localhost:51055"
},
allowCredentials: true,
allowedMethods: "GET, POST, PUT, DELETE, OPTIONS",
allowedHeaders: "Content-Type, Allow, Authorization, Wait, Accept, X-Requested-With, Put-Default-Position, Put-Before, If-Match, If-None-Match, Content-Range",
exposeHeaders: "Content-Range"
));
}
}
Is something wrong i am doing?
You're looking to integrate 2 different frameworks together by using Authentication from MVC (OWIN) with ServiceStack - an isolated framework that doesn't have any coupling or knowledge of OWIN or MVC's Authentication. This is further conflated by trying to transfer Authentication from one domain into a different framework on a different sub domain. Usually trying to try integrate Authentication between completely different frameworks is a difficult endeavor and requiring it to work across sub-domains adds even more complexity.
Storing an Authenticate User Session
With that said the 2 easiest solutions that can work is to store an authenticated UserSession in ServiceStack by serializing an AuthUserSession into the location which ServiceStack expects by using the same distributed Caching Provider configured on both MVC and ServiceStack Apps.
So you can configure ServiceStack to use a Redis CacheClient you can create and store a UserSession in MVC:
var session = new AuthUserSession {
UserAuthId = userId,
DisplayName = userName,
Email = userEmail,
IsAuthenticated = true,
};
Then save it using the configured Redis Manager in MVC:
var sessionId = SessionExtensions.CreateRandomSessionId();
using (var redis = redisManager.GetClient())
{
redis.Set($"urn:iauthsession:{sessionId}", session);
}
To get ServiceStack to use this Authenticated UserSession you need to configure the ss-id Session Cookie Id with sessionId and since you want the client to send the same Cookie to sub-domain you need to configure the Cookie to use a wildcard domain.
Using JWT
The alternative (and my preferred solution) that doesn't require sharing any infrastructure dependencies is to use a stateless JWT Token which encapsulates the Users Session in a JWT Token. To do this in MVC you would create a JWT Token from an Authenticated User Session which you can send to a ServiceStack AppHost configured with the same JwtAuthProvider.
Clients can then make Authenticated Requests by sending JWT Tokens in the JWT Cookie, i.e. sending the JWT Token in the ss-tok cookie which to work across sub-domains needs to be configured to use the same wildcard domain as above.

GoogleTokenResponse does not contain ID token, intermittently

Intermittently, GoogleTokenResponse.parseIdToken() has an NullPointerExpection because the token response does not contain an ID token. Without changing any code, sometimes there is an ID token, and sometimes there isn't. Note that GoogleTokenResponse.getAccessToken() always works.
With no change to any code whatsoever, the ID token will be missing from one minute to the next, even if the access token is always available.
How can I debug this? Where to look?
I get the server auth code using this in an Android client using Google Play Games API:
PendingResult<Games.GetServerAuthCodeResult> pendingResult =
Games.getGamesServerAuthCode(mGoogleApiClient, Constants.web_client_ID);
pendingResult.setResultCallback(new ResultCallback<Games.GetServerAuthCodeResult>() {
#Override
public void onResult(Games.GetServerAuthCodeResult getTokenResult) {
sendToServer(getTokenResult.getCode());
}
});
On the server side (Google Cloud Endpoints), I exchange the code for a token using this code:
try {
tokenResponse = new GoogleAuthorizationCodeTokenRequest(
transport,mJFactory,
"https://www.googleapis.com/oauth2/v4/token",
web_client_ID,web_client_secret,
authCode,
"")
.execute();
} ...
String accessToken = tokenResponse.getAccessToken();
GoogleIdToken idToken = null;
try {
idToken = tokenResponse.parseIdToken(); //-- FAILES HERE INTERMITTENTLY!!!!
} ...
Since it seems that Play Games Services does not guarantee an ID token using Games.getGamesServerAuthCode one should follow the directions in this post and get the ID token as it recommends:
Once you have the access token, you can now call
www.googleapis.com/games/v1/applications//verify/ using that
access token. Pass the auth token in a header as follows:
“Authorization: OAuth ” The response value will contain
the player ID for the user.
See this for a full example.

Create/Login User from a mobile device using Symfony2 and FOSUserBundle

Firstly, I want to create a user sending a post-request from my android app to the server, which uses Symfony2 and the FOSUserBundle.
Finally, I want to login a user from the mobile app and then communicate data with the server.
I know how to implement a post-request on the android-device. But I don't know how I need to configure the FOSUserBundle and security.yml etc to fit my needs. Although I might need a _csrf_token or something and I dont know where to get it from.
I already changed the authentication method from form_login to http_basic and think that this will be the easiest way of doing the authentication (using https to secure the passwords).
But now.. what do I need to do, to achieve the creating and logging in actions without forms? What do I need to put in the post-request on the mobile device?
Thanks for any ideas, comments and solutions!!
A late answer, but it might help.
I'm working on a similar situation and I got this:
In security.yml
security:
providers:
fos_userbundle:
id: fos_user.user_manager
firewalls:
main:
pattern: ^/
stateless: true
http_basic:
realm: "API"
access_control:
- { path: /, role: ROLE_USER }
role_hierarchy:
ROLE_OWNER: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
In config.yml:
fos_user:
db_driver: orm
firewall_name: main
user_class: <your user class>
In my test-method:
Reference: Authentication for a Symfony2 api (for mobile app use)
public function testAuthentication()
{
$client = $this->createClient();
// not authenticated
$client->request('GET', '<url>');
$this->assertEquals(401, $client->getResponse()->getStatusCode());
// authenticated
$client->request('GET', '<url>', array(), array(), array(
'PHP_AUTH_USER' => '<username from your database>',
'PHP_AUTH_PW' => '<password>'
));
$this->assertEquals(200, $client->getResponse()->getStatusCode());
}
For communication with that API, I'd suggest cURL or Buzz
Hope this helps!
Cheers,
Dieter
I had the same problem but I found the solution for registration : (the user enter the username , email and password)
In the UserController of your UserBundle (src/Project/UserBundle/Controller/DefaultController)
define a new function registerAction():
public function registerAction()
{
$user = new User();
$request = $this->getRequest();
$username = $request->request->get('username');
$password= $request->request->get('password');
$email= $request->request->get('email');
$factory = $this->get('security.encoder_factory');
$encoder = $factory->getEncoder($user);
$password = $encoder->encodePassword($password, $user->getSalt());
$user->setPassword($password);
$user->setUsername($username);
$user->setUsernameCanonical($username);
$user->setEmail($email);
$user->setEmailCanonical($email);
$user->setEnabled(true);
$user->setLocked(false);
$user->setExpired(false);
$user->setCredentialsExpired(false);
$em = $this->get('doctrine')->getEntityManager();
$em->persist($user);
$em->flush();
/* $response = new Response(json_encode(array('user' => $tes)));
$response->headers->set('Content-Type', 'application/json');
return $response;*/
return new JsonResponse('good');
}
}
and don't forgot to import :
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder;
use Telifoon\UserBundle\Entity\User;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
in UserBundle/Resources/config/routing.yml add follwoing route:
inscription_post:
pattern: /v1/api/register
defaults: { _controller: ProjectUserBundle:Default:register }
requirements:
_method: POST
My entity ( src/Project/UserBUndle/Entity/User) is :
use FOS\UserBundle\Model\User as BaseUser;
/**
* User
*/
class User extends BaseUser
{
public function __construct()
{
parent::__construct();
// your own logic
}
}
If test the user is added correctely to my database :)

Categories

Resources