Firebase OAuth login & data access with axios - javascript

I am pretty new to Axios and very new to OAuth and Firebase, so I'm sure I'm missing something dumb...
I am trying to create a sign in using firebase's auth provider functions & then create a user profile in my database using Axios. (I have to make a ton of other API calls based on the data I receive and it would be very convenient to just use Axios for everything.)
Here is what I have so far.
authenticate() {
var provider = new firebase.auth.GithubAuthProvider();
console.log(provider);
firebase.auth().signInWithPopup(provider)
.then((res) => {
console.log(res);
if (res.credential) {
var token = res.credential.accessToken;
}
const user = axios.create({
baseURL: fbaseUrl,
withCredentials: true, // newly added
headers: {
Authorization: `Bearer ${token}`, // cf firebase docs https://firebase.google.com/docs/reference/rest/database/user-auth
}
});
this.setState({uid: res.user.uid, useraxios: user, token: token});
}).catch((err) => {
console.log(err.message);
});
}
testPost() {
this.state.useraxios.post(`/users.json`, { id: this.state.uid, joinedOn: moment() })
.then((res) => console.log(res))
.catch((err) => console.log(err.message)); /// this errors out
}
The error I'm currently getting is that there is no 'Access-Control-Allow-Credentials' header and therefore localhost is not allowed access, which I assume is something in the Firebase rules that I have to sort through. Before I added the withCredentials: true line, I was just getting the "not allowed access" response.
I have also tried
const user = axios.create({
baseURL: `${fbaseUrl}/users/${res.user.uid}.json?auth=${token}`
});
and
firebase.auth().currentUser.getToken(true).then((token) => {
const user = axios.create({
baseURL: `${fbaseUrl}/users/${res.user.uid}.json?auth=${token}`
});
and
firebase.auth().currentUser.getToken(true).then((token) => {
const user = axios.create({
baseURL: `${fbaseUrl}`,
headers: {Authorization: token}
});
as per this stackoverflow question which returns the 401 Unauthorized error.
Posting to the database is totally fine when I have both read & write set to true, so it's not a problem with how I'm formatting the URL or something.
I am assuming there are a couple of problems, one with my axios.create config and another with my Firebase rules, but I have gone through the documentation for both and am still very much at a loss. This is a react app but I'm 98% sure the react stuff isn't the problem.
Am I at least on the right track? (Am I a fool to try to use axios for something that would be better suited to firebase's built-in methods...?) Any help would be deeply appreciated.

It is related to your functions configuration. You need to add this in your firebase functions / index.js and configure your function with cors.
const cors = require('cors')({origin: true});
For more details please refer to this url: Enabling CORS in Cloud Functions for Firebase

Related

Axios doesn't create a cookie even though set-cookie header is there?

Front-End: [Axios]
const formSubmit = async (e) => {
e.preventDefault()
const formData = new FormData(e.target)
const email = formData.get('email')
const password = formData.get('password')
try {
const res = await axios.post('http://172.16.2.19:3001/api/v1/auth/login', {
email,
password,
})
console.log(res.data) // its okay, I can login if email & password are correct.
} catch (error) {
console.log(error)
}
}
Back-End [Nodejs ExpressJs]:
Inside App.js:
const cors = require('cors')
app.use(cors({ credentials: true }))
Inside Login.js (/auth/login endpoint):
// ... code, then... if email & password are correct:
// 3600000ms = 1hour
res.cookie('jwt', token, { httpOnly: true, expires: new Date(Date.now() + 3600000 })
res.status(200).json({
status: 'success'
token,
data: userDoc,
})
Then, when I login in my browser:
I can login successfully, but no cookies will be created, see:
The front-end http service (react app) is running on http://172.16.2.19:3000
The back-end http service (expressjs) is running on http://172.16.2.19:3001
The axios requests I'm sending from the front-end are requesting: http://172.16.2.19:3001
So what's the problem?
The problem that no cookies are getting created in the browser is preventing me from continuing to design the front-end application, because if I wanted to request anything from my API, I have to be authenticated, all the routes on the API I made are protected, so if I wanted to request anything from the API, I will have to send my jwt token along with the request.
edit **:
here's the response from the /auth/login endpoint after successfully logging in:
I am using brave browser, the latest version.
I tried this on firefox, it has the same behavior.
GUYS GUYS GUYS I found it!!!! after 3 hours of researching, let me save your time:
For anyone having the same problem, all you have to do is
change your backend code from:
const cors = require('cors')
app.use(cors({ credentials: true }))
to
app.use(cors({ credentials: true, origin: true }))
and make sure you're using withCredentials: true on the front-end with every request (the login POST method and all the other requests that requires authentication)
why?
setting origin property to true is going to reflect the request origin, the origin property can be a string if you wanted to specify a particular domain, ex: http://localhost:3000. But if you have more than one client, setting this to true is a wise choise.
and for those of you wondering about mobile devices in case of specifying a string for the origin field with one particular domain. This problem of cors only happens in browsers, any other client doesn't use that CORS policy.
I would check by passing {withCredentials: true} as the third argument to the axios method to allow the browser to set the cookie via the request.
I don't think it is correct to use the backend to save cookies, as cookies is a browser feature separate from the database. I might be wrong though. When the post is successful, res will return a token. You save this token in the browser's local storage.
const formSubmit = async (e) => {
e.preventDefault()
const formData = new FormData(e.target)
const email = formData.get('email')
const password = formData.get('password')
try {
const res = await axios.post('http://172.16.2.19:3001/api/v1/auth/login', {
email,
password,
})
//browsers local storage
localStorage.setItem('access_token',res.data.access);
localStorage.setItem('refresh_token',res.data.refresh);
console.log(res.data) // its okay, I can login if email & password are correct.
}
You will then have to create an authorization header as such
headers:{
Authorization: localStorage.getItem('access_token')
? 'JWT '+localStorage.getItem('access_token')
: null
}

GET method gives success but PUT method gives 401 despite same credentials

The problem Overview
When getting products using a GET method everything works
when trying to update a product using PUT method I get 401 unauthorized
I use same credentials for both methods
The bearer token is global scope, I should be authorized to do anything to a given product
The code
GET
// javascript
const axiosOptions = {
headers: {
"Authorization": `Bearer ${token}`
}
}
const onAllClick = async () => {
const result = await axios.get(`https://api.printify.com/v1/shops/${shopId}/products.json?limit=30`, axiosOptions );
const products = result.data.data;
console.log(products);
}
PUT
javascript
const axiosTagUpdateOptions = {
headers: {
"Authorization": `Bearer ${token}`
},
data: {
title:"New product title"
}
}
const onEditTagsClick = async (productID, tags) => {
await axios.put(`https://api.printify.com/v1/shops/${shopId}/products/60e0a5c198be4c1296798c27.json`, axiosTagUpdateOptions)
.catch(err => console.log(err));
}
Problem
The get function works perfectly fine. But whenever I try to modify a product using the put method, like the documentation says, I get a 401 unauthorized error. But I'm using the same token for both the get and the put method. The token is set to global scope (for testing purposes) so I should be able to edit and delete products at will.
I read through the API documentation and it looks like I got everything right. I have the bearer token as the header and in the body I included the value I want to modify. Obviously the request is going through, I just don't know why it keeps saying I'm unauthorized to edit the product. I would have liked to edit the product from the printify UI, but the UI won't let me add tags (which is really annoying)... So I'm forced to update via a put method.
I couldn't find any reason for why I am getting this error, can anyone help me?
For put request you must pass data as a parameter and not in axiosOptions.
const onEditTagsClick = async (productID, tags) => {
await axios.put(`https://api.printify.com/v1/shops/${shopId}/products/60e0a5c198be4c1296798c27.json`, data, axiosTagUpdateOptions)
.catch(err => console.log(err));
}

Retrieving firebase topics data for app instance - 401 error

I'm trying to retrieve a list of subscribed FCM topics for an app instance.
The documentation states that I should make a GET request at https://iid.googleapis.com/iid/info/<IID_TOKEN>?details=true, passing in Authorization:key=<WEB_API_KEY> as a header.
Here's how the request looks inside my client:
const getTopics = (token) => {
fetch('https://iid.googleapis.com/iid/info/${token}?details=true', {
headers: {
'Content-Type': 'application/json',
'Authorization': 'key=AIzaSy...dQ80g'
}
})
.then(res => console.log(res))
.catch(err => console.log(err))
}
const messaging = firebase.messaging()
messaging
.requestPermission()
.then(() => messaging.getToken())
.then(getTopics)
.catch(err => console.log(err))
And this is the response I'm getting:
Any ideas what I'm doing wrong here? Clearly one of my tokens is invalid, but according the documentation this should be correct.
In case anyone finds this question and needs an answer, I found the issue: the documentation is wrong, the Authorization header requires the cloud messaging project SERVER KEY. This is NOT the same as the general API key.
Here's where you can find the correct key as of Oct 2019:
Google, please keep your documentation up to date, I wasted hours trying to figure this out!

Creating a YouTube playlist with React using Google's API

I would like to create a YouTube playlist on a users account, but I have struggled to authenticate a POST to the YouTube v3 api.
I'll start by showing how far I have got with this problem.
YouTube API Documentation
The Youtube API Documentation provides details on creating a playlist, and has a working example in the API Explorer
I entered the following code into the request body:
{
"snippet":
{
"title":"Test Playlist"
}
}
This successfully created a playlist on my YouTube account with the same title. So from this I could tell that, a title was required within the body and it would require OAuth 2.0 authentication (an error is displayed if it is not enabled) using one the scopes: youtube, youtube.force-ssl, youtubepartner.
First attempt in react
The First thing I tried was similar to this:
fetch('/youtube/v3/playlists', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Bearer' + api.youtube,
},
body: JSON.stringify({
"snippet":
{
"title":"Test"
}
})
}).then(response => response.json()).then(data => {
console.log(data)
})
api.youtube contains my YouTube api key.
Most of the formatting for this came from another API I have in the same program for getting data from spotify which works.
The response I got from this would say "Login failed" or "Authentication Error" (something along those lines)
Anyway, this is relevant because I know that my first hurdle is getting authentication.
Authentication
The YouTube API Documentation contains a guide titled Implementing OAuth 2.0 Authorization I followed the guide for client side web apps.
The first thing I noticed is that they are using a library, I found this on npm under googleapis and installed it.
When I tried to call this in React using
const {google} = require('googleapis');
I won't get deep into the error but react said "Can't convert undefined to object" and found an issue which said that googleapis is intended for server side not client side, I tried building the react app and putting it on herokuapp but got the same error. Someone else suggested using gapi-client on npm which is a node wrapper for googleapis.
The next thing I did was try the example on the npm page, which is very similar to the google example for configuring the client object. I have it so the import part and function are at the top of my app.js and then the gapi.load part activates after a button is pressed (this could be useless info but w/e)
import gapi from 'gapi-client';
//On load, called to load the auth2 library and API client library.
gapi.load('client:auth2', initClient);
function initClient() {
gapi.client.init({
discoveryDocs: ["https://www.googleapis.com/discovery/v1/apis/drive/v3/rest"],
clientId: 'YOUR_CLIENT_ID',
scope: 'https://www.googleapis.com/auth/drive.metadata.readonly'
}).then(function () {
// do stuff with loaded APIs
console.log('it worked');
});
}
I copied my client ID in from the API Console and this is the exact response I got:
FireFox
Loading failed for the with source
“https://apis.google.com//scs/apps-static//js/k=oz.gapi.en.WcpMzqgmJZU.O/m=auth2,client/rt=j/sv=1/d=1/ed=1/am=AQ/rs=AGLTcCNsTS1p4dx0iMhlrwEpiaXw4iMjOg/cb=gapi.loaded_0”.
Chrome
GET
https://apis.google.com//scs/apps-static//js/k=oz.gapi.en.WcpMzqgmJZU.O/m=auth2,client/rt=j/sv=1/d=1/ed=1/am=AQ/rs=AGLTcCNsTS1p4dx0iMhlrwEpiaXw4iMjOg/cb=gapi.loaded_0
net::ERR_ABORTED 404
That's about as far as I got and I'm not sure what to do from here, so any help is much appreciated. I hope this didn't get too convoluted but I've tried to convey my problem as clearly as possible.
So I was able to authorize the YouTube API and create a playlist.
I have a backend hosted on localhost:8888 (doesn't matter just not what react is hosted on).
here is sample code for what I put in the server.js file (for the backend)
var express = require('express');
var app = express();
var passport = require('passport');
app.use(passport.initialize());
var YoutubeV3Strategy = require('passport-youtube-v3').Strategy;
passport.use(new YoutubeV3Strategy({
clientID: YOUR_CLIENT_ID,
clientSecret: YOUR_CLIENT_SECRET,
callbackURL: 'http://localhost:8888/redirect',
scope: ['https://www.googleapis.com/auth/youtube']
},
function (accessToken, refreshToken, profile, cb) {
var user = {
accessToken: accessToken,
refreshToken: refreshToken
};
return cb(null, user)
}
));
passport.serializeUser(function(user, cb) {
cb(null, user);
});
passport.deserializeUser(function(obj, cb) {
cb(null, obj);
});
app.get('/authenticate', passport.authenticate('youtube'))
app.get('/redirect', passport.authenticate('youtube', { failureRedirect: '/login' }),
function(req, res) {
res.redirect('http://localhost:3000' + '?access_token=' + req.user.accessToken)
})
app.listen(8888)
This is using Passport.js to do oauth for me, lots of documentation can be found on the site.
In react I have it so a button will open localhost:8888/authenticate and then that will redirect back to my application. If you are using this you need to make sure that on your google API credentials you have the javascript origin as http://localhost:8888 and the redirect URI as http://localhost:8888/redirect and the correct scope and application type.
This is the function I use in my app.js (react) to make the POST
getAPIdata() {
let parsed = queryString.parse(window.location.search);
let accessToken = parsed.access_token
fetch('https://www.googleapis.com/youtube/v3/playlists?part=snippet', {
method: 'POST',
headers: {
'Content-type': 'application/json',
'Authorization': 'Bearer ' + accessToken,
},
body: JSON.stringify({
'snippet':
{
'title':this.state.inputTitle
}
})
}).then(response => response.json()).then(data => {
console.log(data)
window.alert('https://www.youtube.com/playlist?list=' + data.id)
})
}
I was actually mostly correct with the first attempt I just had the authorization incorrect.
Here's a couple sources that helped me make my solution:
Passport.js oauth tutorial
Googles OAuth 2.0 Playground
Passport.js Documentation
Passport.js facebook oauth example
Hopefully this is helpful to someone, You can use the same code i used in server.js to authenticate most services by just changing the strategy.
A live version of my application can be found here. In the console it shows the response from the POST request, this should help if you have any issues. I know the alert is bad ui but this wasn't the intended use.
Thanks for reading :)

Use apollo-link-state to inject data into an ApolloLink Object

I stored my token to authenticate with my GraphQL API by adding a JWT to the header inside an apollo-link-states #client property.
query ClientToken() {
clientToken #client
}
I now want to use that token to authenticate my Apollo remote queries. Without using the local cache, doing the following works:
const authLink = new ApolloLink((operation, forward) => {
operation.setContext({
headers: {
Authorization: `Bearer GETMEATOKEN`
}
})
return forward(operation)
})
I'm struggeling to find a way to query this locally stored token inside this operation to add it where currently GETMEATOKEN appears.
Anyone has a suggestion if/who to query for a locally stored property inside an ApolloLink?
Thanks for all suggestions

Categories

Resources