Token based authentication http interceptor - symfony

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

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 email verification from server side

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.

How to reconcile Firebase Auth token refreshing with Server-Side Rendering

We're using Firebase in a Next.js app at work. I'm new to both, but did my best to read up on both. My problem is more with Firebase, not so much with Next.js. Here's the context:
In the client app, I make some calls to our API, passing a JWT (the ID token) in an Authorization header. The API calls admin.auth().verifyIdToken to check that the ID token is fresh enough. This works fine, since I am more or less guaranteed that the ID token gets refreshed regularly (through the use of onIDTokenChanged (doc link)
Now I want to be able to Server-Side Render my app pages. In order to do that, I store the ID token in a cookie readable by the server. But from here on, I have no guarantee that the ID token will be fresh enough next time the user loads the app through a full page load.
I cannot find a server-side equivalent of onIDTokenChanged.
This blog post mentions a google API endpoint to refresh a token. I could hit it from the server and give it a refresh token, but it feels like I'm stepping out of the Firebase realm completely and I'm worried maintaining an ad-hoc system will be a burden.
So my question is, how do people usually reconcile Firebase auth with SSR? Am I missing something?
Thank you!
I've had that same problem recently, and I solved by handling it myself. I created a very simple page responsible for forcing firebase token refresh, and redirecting user back to the requested page. It's something like this:
On the server-side, check for token exp value after extracting it from cookies (If you're using firebase-admin on that server, it will probably tell you as an error after verifying it)
// Could be a handler like this
const handleTokenCookie = (context) => {
try {
const token = parseTokenFromCookie(context.req.headers.cookie)
await verifyToken(token)
} catch (err) {
if (err.name === 'TokenExpired') {
// If expired, user will be redirected to /refresh page, which will force a client-side
// token refresh, and then redirect user back to the desired page
const encodedPath = encodeURIComponent(context.req.url)
context.res.writeHead(302, {
// Note that encoding avoids URI problems, and `req.url` will also
// keep any query params intact
Location: `/refresh?redirect=${encodedPath}`
})
context.res.end()
} else {
// Other authorization errors...
}
}
}
This handler can be used on the /pages, like this
// /pages/any-page.js
export async function getServerSideProps (context) {
const token = await handleTokenCookie(context)
if (!token) {
// Token is invalid! User is being redirected to /refresh page
return {}
}
// Your code...
}
Now you need to create a simple /refresh page, responsible for forcing firebase token refresh on client-side, and after both token and cookie are updated, it should redirect user back to the desired page.
// /pages/refresh.js
const Refresh = () => {
// This hook is something like https://github.com/vercel/next.js/blob/canary/examples/with-firebase-authentication/utils/auth/useUser.js
const { user } = useUser()
React.useEffect(function forceTokenRefresh () {
// You should also handle the case where currentUser is still being loaded
currentUser
.getIdToken(true) // true will force token refresh
.then(() => {
// Updates user cookie
setUserCookie(currentUser)
// Redirect back to where it was
const decodedPath = window.decodeURIComponent(Router.query.redirect)
Router.replace(decodedPath)
})
.catch(() => {
// If any error happens on refresh, redirect to home
Router.replace('/')
})
}, [currentUser])
return (
// Show a simple loading while refreshing token?
<LoadingComponent />
)
}
export default Refresh
Of course it will delay the user's first request if the token is expired, but it ensures a valid token without forcing user to login again.

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.

Check logged user with normal and ajax request

I use interceptor to check if a user is logged in every controller call like this :
public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler) {
if(request.getSession().getAttribute("user") == null) {
response.sendRedirect("redirect:/login?next="+
URLEncoder.encode(
request.getRequestURL().toString() + "" +
(request.getQueryString() != null ? "?" + request.getQueryString() : "")
,"utf-8");
return false;
}
return true;
}
It work fine for normal request but for ajax request i can't make a response.sendRedirect(..).
How to know if it's a ajax or normal request ?
How can i do it like if i got a ajax error ?
$.ajax({
.....
success : function(data) { ...... },
error : function(){
alert("login error"); // or
document.location = '/path/login' // or something else
}
});
There a other way to handle it rather than using interceptor ?
1. How to know if it's a ajax or normal request ?
You can check inside your interceptor for the existence of the X-Requested-With header. This header is always added to the ajax request by the jQuery library (to my knowing almost all major js libraries add it as well) with the purpose of preventing the Cross-Site request forgery. To figure out if the request is ajax, you can write your preHandle method like
public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler) {
String requestedWith = request.getHeader("X-Requested-With");
Boolean isAjax = requestedWith != null ? "XMLHttpRequest".equals(requestedWith) : false;
...
}
2. How can i do it like if i got a ajax error ?
As you've already noticed, ajax request don't recognize server side redirects, as the intention of the server side redirects is to be transparent to the client. In the case of an ajax request, don't do redirect rather set some status code to the response e.g. response.setStatus(respCode) or add a custom header e.g. response.setHeader("Location", "/path/login"), and read it through in the jQuery's complete method which is a callback that follows after either success or error, e.g.
$.ajax({
//...
complete: function(xhr, textStatus) {
console.log(xhr.status);
console.log(xhr.getResponseHeader('Location'));
// do something e.g. redirect
}
});
3. There a other way to handle it rather than using interceptor ?
Definitely. Checkout Spring Security. Its a framework, and adds a bit to the learning curve, but its well worth it. It will add much more than a custom solution, e.g. you'll get authorization mechanism on top of the authentication. When your application matures, you'll notice that the straigthforward implementation that you're on to now, has quite a few security flaws that are not hard to exploit e.g. session fixation, where spring security can easily protect you. There's plenty of examples online, and you'll get better support here on the SO in comparison to any custom solution. You can unit test it, an asset I personally value very much
You could simply:
Refuse ajax requests before the user is properly logged in
once the user logs in, set a security token in the session or somewhere
pass that token in the ajax request and use that token to validate on the server side prehandle
in your case you would check the existence of the token before running into the code
Also, the preHandle does not have to apply to every routes, you could also have different routes each with different authorisation, prehandle, code.

Resources