Firebase email verification from server side - firebase

I have a link to default email verification function in Firebase.
Using this link from the browser works fine, however it fails when being used from server side with the following code:
try {
const url = `https://example.com/__/auth/action?mode=verifyEmail&oobCode=${oobCode}&apiKey=${apiKey}&lang=en`;
const response = await axios.get(url);
if (response.data.success) {
return next();
} else {
return next(new ErrorResponse("Failed email verification", FORBIDDEN));
}
} catch (error) {
return sendFailedWithErr(res, error.message);
}
When I am copying the URL used in the server side the exact same URL works from the browser, but fails on the server side.
Would appreciate any idea what is the problem.

This is because a call to this URL is not going to return a response that you can check like the response of a REST API endpoint with, e.g. response.data.success.
As you will see here, this URL is supposed to be used to open a web page in which you will:
Get the values passed as QueryString parameters (e.g. mode or oobCode)
Call, from the web page some methods of the Firebase JavaScript SDK, like applyActionCode() in the case of email verification.
You may be able to mimic this action from a server, but I've never tried.

Related

Handle Firebase client-side token and access to protected pages

I'm using Firebase auth to login with Facebook, Google and email/pass. Basically, everything runs client-side, I make a call to Firebase and I receive an object containing an access token (that is a JWT ID Token), a customer id and its email. When I get this object, I put it into a persistent store (local storage, I know it's bad) and I perform an API call to one of my sveltekit endpoint that will in turn make another API call to a backend API (written in Go) to get all the user informations: firstname, lastname, phone, address, stats etc. To give a little bit of context, below is a diagram to illustrate what's happening when a user sign-in using Facebook.
Up to now, I just put the Firebase object into a store and just check if the information are there to allow access to a particular page. This check is done in the +layout.svelte page of the directory containing the page to be protected. It looks like something like this:
onMount(() => {
// redirect if not authenticated
if (browser && !$authStore?.uid) goto(`/auth/sign-in`);
});
It's probably not a good thing especially since my store persists in the local storage and therefore is prone to some javascript manipulation.
From my understanding, there's at least 2 things that could be better implemented but I may be wrong:
Set the access token in an httponly cookie straight after receiving it from Firebase. This would avoid storing the access token (that is a JWT) in the local storage and It could be used to authorize access or not to some protected pages...
I receive the Firebase authentication object on client-side buthttp only cookie can only be created from server side. I thought about sending the object as a payload to a POST sveltekit endpoint (like /routes/api/auth/token/+server.js) and set the cookie from here but apparently cookies is not available in a sveltekit endpoint.
So, how and where should I set this cookie ? I see that cookies is available in the load function of a +layout.server.js file, as well as in the handle function of a hooks.server.js file, but I don't see the logic here.
Populate locals.userwith the authenticated user once I've performed a call to my backend. Well, here, it's not obvious to me because I think point 1) would be enough to manage access to protected pages, but I see that a check of locals.user is something I've seen elsewhere.
I tried to set locals.user in the sveltekit endpoint that is making the API call to the backend API:
// /routes/api/users/[uid]/+server.js
import { json } from "#sveltejs/kit";
import axios from "axios";
import { GO_API_GATEWAY_BASE_URL } from "$env/static/private";
export async function GET({ request, locals, params }) {
try {
const config = {
method: "get",
baseURL: GO_API_GATEWAY_BASE_URL,
url: `/users/${params.uid}`,
headers: {
Authorization: `Bearer ${uidToken}`, // <-- the Firebase ID Token
},
withCredentials: true,
};
const res = await axios(config);
// set locals
locals.user = json(res.data); // <--- DOESN'T SEEM TO WORK
return json(res.data);
} catch (error) {...}
}
...but in the +layout.server.js page that I've created I see nothing:
// routes/app/protected_pages/+layout.server.js
import { redirect } from "#sveltejs/kit";
export function load({ locals }) {
console.log(locals); // <----- contains an empty object: {}
if (!locals.user) throw redirect(302, "/auth/sign-in");
}
Thank you so much for your help

Firebase Web Admin

First of all, I am using nodejs for the backend. I use firebase hosting and firebase functions to deploy an express() app.
What I am trying to achieve is to make an admin website, which is connected to Firebase. so I have a route /admin/ like this:
adminApp.get("/", (request, response) => {
return response.redirect("/admin/login");
});
Here I basically want to check if a current user is logged in - or not.
I know firebase supports client side authentication using:
firebase.auth().onAuthStateChanged(user => {
if (user) {
} else {
}
});
And using
function login() {
var userEmail = document.getElementById("email").value;
var userPass = document.getElementById("password").value;
firebase.auth().signInWithEmailAndPassword(userEmail, userPass).catch(function(error) {
var errorCode = error.code;
var errorMessage = error.message;
if (error) {
document.getElementById('loginError').innerHTML = `Error signing in to firebase`;
}
});
}
However image this case:
Someone (not an admin) is visiting /admin/some_secret_website/ which he obviously does not have access to.
If I rely on client side authentication, it first loads the entire website and the scripts and then notices - hey I am not authenticated, let me redirect to /login. By then however anyone knows the source code of an admin page.
I'd rather have something like:
adminApp.get("/admin/some_secret_website", (request, response) => {
if (request.user) {
// user is authenticated we can check if the user is an admin and give access to the admin page
}
});
I know that you can get the user's token and validate that token using the AdminSDK, but the token must be send by the client code, meaning the website was already loaded.
I came across Authorized HTTPS Endpoint by firebase, but it only allows a middleware when using a bearer token.
Does anybody know how I can maintain a server side user object to not even return admin html to the browser but only allow access to admins?
Like Doug indicated, the way your admin website/webapp would function with Firebase Cloud Functions (which is effectively a Nodejs server) is that you get the request, then use the headers token to authenticate them against Firebase Auth. See this answer for a code snippet on this.
In your case, I'm thinking you would create a custom claim for an "administrator" group and use that to determine whether to send a pug templated page as a response upon authentication. As far as Authorization, your db rules will determine what said user can CRUD.

Token based authentication http interceptor

I would like your opinion in order to achieve something. I have an api and I want to restrictionate the access of the user. But short story long, in action login controller of page /login I generate the token for the user autheticated and I return it. Well, here comes one of my question/problem. It's mandatory in a action controller to return a response, and my token it's send in json format and it's displayed on browser, but I don't want this, I just want to keep in response or in something so that my angular part to take that token and to take care of it in its way. So my method login:
public function loginAction(Request $request)
{
$username= $this->get('security.token_storage')->getToken()->getUser()->getEmail();
$serviceUser = $this->container->get('ubb_user_login');
$tokenUser = $serviceUser->generateToken($username);
$response = new JsonResponse(array('Token' => $tokenUser));
return $response;
}
2nd problem. I do not know in angular where to extract the token. In a controller I tried something:
app.controller('loginApi', function ($scope, $http, $window){
$http.get(
'/api/user/login'
).then(function (success) {
$scope.token = success.data.Token;
$window.localStorage.setItem('Token', token); // it's okay used like this localStorage?
});
});
Here, I want only to take the token from /login action, to save it in local storage, and then to display it on every request using $http request interceptor. That part works. If I send a random string in header auth it's working and it gives me access to api:
function httpInterceptor() {
return {
request: function(config) {
config.headers.authorization = 'jdjdnnkdkd';
return config;
},
responseError: function(errorResponse) {
switch (errorResponse.status) {
case 403:
window.location = '/login';
break;
case 401:
window.location = '/login';
}
return $q.reject(errorResponse);
}
}
}
So, the problems I encounter:
1) How to send the token in order for angular to take it, but to not be displayed in the browser? Let's say that I have a menu that access the api, and if the user it's not authenticated,clicking on a link unauthenticated it's sending me on the login page, and after I auth with success, to redirect me on the menu?
2) After returning the token, where exactly to use it in angular? In which controller? And how to save it?
Much appreciation, thank you.
It seems like you need a separate api resource for working with the tokens. Have you tried using FOSOAuthServeBundle? It helps a lot with setting up oauth authentication, tokens etc.
In general you need to have a separate call for the token, i.e.:
angluar app makes request to a get token resource, a token is returned and temporarily stored in the angular app
use that token for each subsequent request - check the oauth bundle config on how to set which urls have to be oauth protected, but that bundle takes care of this for you
Regarding your angular issue, look at this q/a How to store authentication bearer token in browser cookie using AngularJS

Apigee 401 Unauthorized with token_expired

I'm trying to create a user account through the apigee JS API. This worked just fine when I was last doing this before the holidays in mid December. Now, however, I get a 401 Unauthorized error reading token_expired.
Is there a way to refresh the token? I don't know why it would have expired.
This is what I'm trying. First I instantiate the data client. No problems here:
var dataClient;
var client_creds = {
orgName: '*******',
appName: '*******'
}
dataClient = new Apigee.Client(client_creds);
Later, when trying to create a new user, I get the token_expired error:
dataClient.request(options, function (error, response) {
if (error) {
console.log(response);
alert("Something went wrong when trying to create the user. " + response.error)
// Error
} else {
// Success - the user has been created, now login.
dataClient.login(user.email, user.password,
function (err) {
if (err) {
//error - could not log user in
console.log("There was an error logging in " + user.name);
} else {
//success - user has been logged in
}
}
);
}
});
I've also tried dataClient.signup, but same error.
There are no refresh tokens within App Services; you'll need to follow the login flow in order to retrieve a new token. Note that you can specify the ttl parameter, like so, so you don't need to do this as frequently:
https://api.usergrid.com/{org}/{app}/token?ttl=604800
By default, this is set to 7 days, but you can change the default app max ttl to 0 (non-expiring) or something else like 31104000000 (365 days).
To do that, you make a PUT request:
https://api.usergrid.com/{org}/{app}/?client_id={app_client_id}&client_secret={app_client_secret}
With JSON payload:
{
"accesstokenttl":0
}
Or for 1 year:
{
"accesstokenttl":31104000000
}
If that doesn't work for you, the authorization tokens for the JavaScript SDK are kept in your browser's local storage. In Chrome, use the Developer Tools. In the Resources tab on the left hand side expand the Local Storage entry. You should see something like "http://usergrid.dev" or something similar. Choose that and on the right hand side you should see an entry for accessToken. Delete that and it should solve your problem.

Meteor.http.call gives not allowed by Access-Control-Allow-Origin

When I try to call an external server for JSON queries in Meteor with the Meteor.http.call("GET") method I get the error message "not allowed by Access-Control-Allow-Origin".
How do I allow my meteor app to make HTTP calls to other servers?
Right now I run it on localhost.
The code I run is this:
Meteor.http.call("GET",
"http://api.vasttrafik.se/bin/rest.exe/v1/location.name?authKey=XXXX&format=json&jsonpCallback=processJSON&input=kungsportsplatsen",
function(error, result) {
console.log("test");
}
);
There are other questions similar to this on StackOverflow.
You're restricted by the server you're trying to connect to when you do this from the client side (AJAX).
One way to solve it is if you have access to the external server, you can modify the header file to allow some, or all origins by:
Access-Control-Allow-Origin: *
However, if you place the call on the server side and not provide a callback function, the call will be made synchronously, thus not with AJAX, and it should succeed.
Here's
Meteor.methods({checkTwitter: function (userId) {
this.unblock();
var result = Meteor.http.call("GET", "http://api.twitter.com/xyz", {params: {user: userId}});
if (result.statusCode === 200) return true
return false;
}});

Resources