I am using Firebase authentication for my application and using it to authenicate users to a back-end API using JWT tokens. On the API back-end I've configured the JWT-secret, which is the asymmetric keys pulled from this url:
https://www.googleapis.com/service_accounts/v1/jwk/securetoken#system.gserviceaccount.com
This is all working fine. I recently needed to create a cloud function, which needs to call the API back-end as well. To do this, I'm using the functionality to create a Custom Token found here:
https://firebase.google.com/docs/auth/admin/create-custom-tokens
This creates my token with correct custom claims
let additionalClaims = {
'x-hasura-default-role': 'admin',
'x-hasura-allowed-roles': ['user', 'admin']
}
admin.auth().createCustomToken(userId,additionalClaims).then(function (customToken) {
console.log(customToken);
response.end(JSON.stringify({
token: customToken
}))
})
.catch(function (error) {
console.log('Error creating custom token:', error);
});
however, when I try to use it against the back-end API, I get the "JWTInvalidSignature" error. In my cloud function, I specify the service account that is in my firebase project, but it doesn't seem to help. When I view the two tokens decoded, they definitely appear coming from different services.
CustomToken
{
"aud":
"https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
"iat": 1573164629,
"exp": 1573168229,
"iss": "firebase-adminsdk-r2942#postgrest-b4c8c.iam.gserviceaccount.com",
"sub": "firebase-adminsdk-r2942#postgrest-b4c8c.iam.gserviceaccount.com",
"uid": "mikeuserid",
"claims": {
"x-hasura-default-role": "admin",
"x-hasura-allowed-roles": [
"user",
"admin"
]
}
}
TOKEN from FireBase Auth
{
"role": "webuser",
"schema": "customer1",
"userid": "15",
"claims": {
"x-hasura-default-role": "user",
"x-hasura-allowed-roles": [
"user",
"admin"
],
"x-hasura-user-id": "OS2T2rdkM5UlhfWLHEjNExZ71lq1",
"x-hasura-dbuserid": "15"
},
"iss": "https://securetoken.google.com/postgrest-b4c8c",
"aud": "postgrest-b4c8c",
"auth_time": 1573155319,
"user_id": "OS2T2rdkM5UlhfWLHEjNExZ71lq1",
"sub": "OS2T2rdkM5UlhfWLHEjNExZ71lq1",
"iat": 1573164629,
"exp": 1573168229,
"email": "johnny1#gmail.com",
"email_verified": false,
"firebase": {
"identities": {
"email": [
"johnny1#gmail.com"
]
},
"sign_in_provider": "password"
}
}
How can I get this customToken to work with the existing JWT secret keys I have configured??
As documented in in Firebase Authentication: Users in Firebase Projects: Auth tokens, the tokens from Firebase Auth and the Admin SDK Custom Tokens are not the same, incompatible with each other and are verified differently.
Edited response after clarification:
As you are trying to identify the cloud functions instance as an authorative caller of your third-party API, you may use two approaches.
In both of the below methods, you would call your API using postToApi('/saveUserData', { ... }); in each example. You could probably also combine/support both server-side approaches.
Method 1: Use a public-private key pair
For this version, we use a JSON Web Token to certify that the call is coming from a Cloud Functions instance. In this form of the code, the 'private.key' file is deployed along with your function and it's public key kept on your third-party server. If you are calling your API very frequently, consider caching the 'private.key' file in memory rather than reading it each time.
If you ever wish to invalidate this key, you will have to redeploy all your functions that make use of it. Alternatively, you may modify the fileRead() call and store it in Firebase Storage (secure it - readable by none, writable by backend-admin). Which will allow you to refresh the private key periodically by simply replacing the file.
Pros: Only one remote request
Cons: Updating keys could be tricky
const jwt = require('jsonwebtoken');
const rp = require('request-promise-native');
const functionsAdminId = 'cloud-functions-admin';
function getFunctionsAuthToken(jwtOptions) {
jwtOptions = jwtOptions || {};
return new Promise((resolve, reject) => {
// 'private.key' is deployed with function
fs.readFile('private.key', 'utf8', (err, keyData) => {
if (err) { return reject({src: 'fs', err: err}); }
jwt.sign('cloud-functions-admin', keyData, jwtOptions, (err, token) => {
if (err) { return reject({src: 'jwt', err: err}); }
resolve(token);
});
});
});
}
Example Usage:
function postToApi(endpoint, body) {
return getFunctionsAuthToken()
.then((token) => {
return rp({
uri: `https://your-domain.here${endpoint}`,
method: 'POST',
headers: {
Authorization: 'Bearer ' + token
},
body: body,
json: true
});
});
}
If you are using express on your server, you can make use of express-jwt to deserialize the token. If configured correctly, req.user will be 'cloud-functions-admin' for requests from your Cloud Functions.
const jwt = require('express-jwt');
app.use(jwt({secret: publicKey});
Method 2: Add a cloud-functions-only user
An alternative is to avoid the public-private key by using Firebase Auth. This will have the tradeoff of potentally being slower.
Pros: No key management needed, easy to verify user on server
Cons: Slowed down by Firebase Auth calls (1-2)
const admin = require('firebase-admin');
const rp = require('request-promise-native');
const firebase = require('firebase');
const functionsAdminId = 'cloud-functions-admin';
function getFunctionsAuthToken() {
const fbAuth = firebase.auth();
if (fbAuth.currentUser && fbAuth.currentUser.uid == uid) {
// shortcut
return fbAuth.currentUser.getIdToken(true)
.catch((err) => {src: 'fb-token', err: err});
}
return admin.auth().createCustomToken(functionsAdminId)
.then(function(customToken) {
return fbAuth.signInWithCustomToken(token)
.then(() => {
return fbAuth.currentUser.getIdToken(false)
.catch((err) => {src: 'fb-token', err: err});
})
.catch((err) => {src: 'fb-login', err: err});
})
.catch((err) => {src: 'admin-newtoken', err: err});
}
Example Usage:
function postToApi(endpoint, body) {
return getFunctionsAuthToken()
.then((token) => {
return rp({
uri: `https://your-domain.here${endpoint}`,
method: 'POST',
headers: {
Authorization: 'Bearer ' + token
},
body: body,
json: true
});
});
}
On your server, you would use the following check:
// idToken comes from the received message
admin.auth().verifyIdToken(idToken)
.then(function(decodedToken) {
if (decodedToken.uid != 'cloud-functions-admin') {
throw 'not authorized';
}
}).catch(function(error) {
// Handle error
});
Or if using express, you could attach it to a middleware.
app.use(function handleFirebaseTokens(req, res, next) {
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
var token = req.headers.authorization.split(' ')[1];
admin.auth().verifyIdToken(idToken)
.then((decodedToken) => {
req.user = decodedToken;
next();
}, (err) => {
//ignore bad tokens?
next();
});
} else {
next();
}
});
// later on: req.user.uid === 'cloud-functions-admin'
Original response:
If your client uses Firebase Authentication from an SDK to log in and your server uses the Admin SDK, you can use the client's ID token on the cloud function to speak to your server to verify a user by essentially "passing the parcel".
Client side
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
// Send token to your cloud function
// ...
}).catch(function(error) {
// Handle error
});
Cloud Function
// idToken comes from the client app
admin.auth().verifyIdToken(idToken) // optional (best-practice to 'fail-fast')
.then(function(decodedToken) {
// do something before talking to your third-party API
// e.g. get data from database/secret keys/etc.
// Send original idToken to your third-party API with new request data
}).catch(function(error) {
// Handle error
});
Third-party API
// idToken comes from the client app
admin.auth().verifyIdToken(idToken)
.then(function(decodedToken) {
// do something with verified user
}).catch(function(error) {
// Handle error
});
Related
I am using fetch to call backend API, th eprobelm is the backend security is a token, for security purpose we can not expose this token on public configuration.
I wanted to know if it is possible to put the token on server side params and then when we call fetch params is not visible in chrome debug and use only on NITRO following this text
Nitro allows 'direct' calling of routes via the globally-available $fetch helper. This will make an API call to the server if run on the browser, but will directly call the relevant function if run on the server, saving an additional API call.
$fetch API is using my fetch, with key features including:
This is my code
let recipientWebDTO = {};
recipientWebDTO.email = this.form.email;
recipientWebDTO.subscriptions = [{
"mailingListUnid": useRuntimeConfig().UNID
}];
const { status } = await $fetch
.raw(useRuntimeConfig().public.REST_API, {
method: "POST",
body: recipientWebDTO,
headers: {
"Content-Type": "application/json",
Authorization: useRuntimeConfig().TOKEN,
},
})
.then((response) => ({
status: response.status,
}))
.catch((error) => ({
status: error?.response?.status || 500,
}));
And my config file
export default defineNuxtConfig({
runtimeConfig: {
UNID: '58',
COMPANY_UNID: '3',
TOKEN: '78f77',
public: {
REST_API: process.env.REST_API || 'http://localhost:8080/rest/mailinglist/register/v1'
},
},
css: ["#/assets/_main.scss"],
vite: {
css: {
preprocessorOptions: {
scss: {
additionalData: '#use "#/assets/_colors.scss" as *;'
}
}
}
}
})
I want UNID, COMPANY_UNID, TOKEN to be visible only on server side, here it is just undefined, have I to create a middleware to handle it ? If yes, how I can use the same project to make it work ?
To manage to do it i added a server part as it is explained here:
Server api nuxtjs3 documentation
I created a directory server/api then my ts file with my proxy call where i use my token. Then in my vue i call my api. it means on browser the server file is invisible, and parameters and token nt accessibles.
in my vue:
const { status } = await $fetch.raw( '/api/newsletter', { method: "POST", body: this.form.email } )
in my server file:
export default defineEventHandler(async (event) => {
const runtimeConfig = useRuntimeConfig();
const subscriber = await readBody(event);
console.log("url used for rest call" + runtimeConfig.REST_API);
let recipientWebDTO = {
email: subscriber,
subscriptions: [{
"mailingListUnid": runtimeConfig.UNID
}]
};
const status = await $fetch.raw(runtimeConfig.REST_API, {
method: "POST",
body: recipientWebDTO,
headers: {
Authorization: runtimeConfig.TOKEN,
},
}).then((response) => ({
status: response.status,
}))
.catch((error) => ({
status: error?.response?.status || 500,
}));
return status;
})
The only complexity i have is i wanted to get a specific status but i always have the api call status, is 200 i wanted to simply forward the result but in NUXTJS is in WIP.
I'm using the following interceptors in a Vuejs v2 website to push a firebase token to my node backend. There in the backend, I detect/verify the token, pull some data using the uid from a database and then process any api calls.
Even though I am using the firebase onIdTokenChanged to automatically retrieve new ID tokens, sometimes, if the user is logged in, yet inactive for an hour, the token expires without refreshing. Now, this isn't a huge deal - I could check in the axios response interceptor and push them to a login page, but that seems annoying, if I can detect a 401 token expired, resend the axios call and have a refreshed token, the user won't even know it happened if they happen to interact with a component that requires data from an API call. So here is what I have:
main.js
Vue.prototype.$axios.interceptors.request.use(function (config) {
const token = store.getters.getSessionToken;
config.headers.Authorization = `Bearer ${token}`;
return config;
});
Vue.prototype.$axios.interceptors.response.use((response) => {
return response }, async function (error) {
let originalRequest = error.config
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
let user = auth.currentUser;
await store.dispatch("setUser", {user: user, refresh: true}).then(() => {
const token = store.getters.getSessionToken;
Vue.prototype.$axios.defaults.headers.common['Authorization'] = 'Bearer ' + token;
return Vue.prototype.$axios.request(originalRequest);
});
}
return Promise.reject(error); });
let app;
auth.onAuthStateChanged(async user => {
await store.dispatch("setUser", {user: user, refresh: false}).then(() => {
if (!app) {
app = new Vue({
router,
store,
vuetify,
render: h => h(App)
}).$mount('#app')
}
})
.catch(error => {
console.log(error);
});
});
vuex
setUser({dispatch, commit}, {user, refresh}) {
return new Promise((resolve) => {
if(user)
{
user.getIdToken(refresh).then(token => {
commit('SET_SESSION_TOKEN', token);
this._vm.$axios.get('/api/user/session').then((response) => {
if(response.status === 200) {
commit('SET_SESSION_USER', response.data);
resolve(response);
}
})
.catch(error => {
dispatch('logout');
dispatch('setSnackbar', {
color: "error",
timeout: 4000,
text: 'Server unavailable: '+error
});
resolve();
});
})
.catch(error => {
dispatch('logout');
dispatch('setSnackbar', {
color: "error",
timeout: 4000,
text: 'Unable to verify auth token.'+error
});
resolve();
});
}
else
{
console.log('running logout');
commit('SET_SESSION_USER', null);
commit('SET_SESSION_TOKEN', null);
resolve();
}
})
},
I am setting the token in vuex and then using it in the interceptors for all API calls. So the issue I am seeing with this code is, I'm making an API call with an expired token to the backend. This returns a 401 and the axios response interceptor picks it up and goes through the process of refreshing the firebase token. This then makes a new API call with the same config as the original to the backend with the updated token and returns it to the original API call (below).
This all seems to work, and I can see in dev tools/network, the response from the API call is sending back the correct data. However, it seems to be falling into the catch of the following api call/code. I get an "undefined" when trying to load the form field with response.data.server, for example. This page loads everything normally if I refresh the page (again, as it should with the normal token/loading process), so I know there aren't loading issues.
vue component (loads smtp settings into the page)
getSMTPSettings: async function() {
await this.$axios.get('/api/smtp')
.then((response) => {
this.form.server = response.data.server;
this.form.port = response.data.port;
this.form.authemail = response.data.authemail;
this.form.authpassword = response.data.authpassword;
this.form.sendemail = response.data.sendemail;
this.form.testemail = response.data.testemail;
this.form.protocol = response.data.protocol;
})
.catch(error => {
console.log(error);
});
},
I have been looking at this for a few days and I can't figure out why it won't load it. The data seems to be there. Is the timing of what I'm doing causing me issues? It doesn't appear to be a CORS problem, I am not getting any errors there.
Your main issue is mixing async / await with .then(). Your response interceptor isn't returning the next response because you've wrapped that part in then() without returning the outer promise.
Keep things simple with async / await everywhere.
Also, setting common headers defeats the point in using interceptors. You've already got a request interceptor, let it do its job
// wait for this to complete
await store.dispatch("setUser", { user, refresh: true })
// your token is now in the store and can be used by the request interceptor
// re-run the original request
return Vue.prototype.$axios.request(originalRequest)
Your store action also falls into the explicit promise construction antipattern and can be simplified
async setUser({ dispatch, commit }, { user, refresh }) {
if(user) {
try {
const token = await user.getIdToken(refresh);
commit('SET_SESSION_TOKEN', token);
try {
const { data } = await this._vm.$axios.get('/api/user/session');
commit('SET_SESSION_USER', data);
} catch (err) {
dispatch('logout');
dispatch('setSnackbar', {
color: "error",
timeout: 4000,
text: `Server unavailable: ${err.response?.data ?? err.message}`
})
}
} catch (err) {
dispatch('logout');
dispatch('setSnackbar', {
color: "error",
timeout: 4000,
text: `Unable to verify auth token. ${error}`
})
}
} else {
console.log('running logout');
commit('SET_SESSION_USER', null);
commit('SET_SESSION_TOKEN', null);
}
}
I have some data that I have created in my Firestore. I am trying to access them using the REST API. But I am getting an authentication error. I am doing the following:
According to the Cloud Firestore documentation, I can
"Generate a Firebase ID token using the Firebase Authentication REST
API."
To get the idToken I am signing in a valid user in the following way:
const API_KEY = 'my API key'
const URL_GET_TOKEN_BY_SIGN_IN = `https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${API_KEY}`;
const getTokensBySigningIn = () => {
return new Promise(async (resolve, reject) => {
const res = await fetch(URL_GET_TOKEN_BY_SIGN_IN, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: `registered_user_email#somedomain.com`,
password: "valid_password",
returnSecureToken: true,
}),
}).catch((err) => {
reject(err);
});
const resData = await res.json().catch((err) => {
reject(err);
});
resolve(resData);
});
};
The returned promise resolves with an object of the form:
{
"kind": "identitytoolkit#VerifyPasswordResponse",
"localId": "ZgnICJLhiJM5... some local id",
"email": "registered_user_email#somedomain.com",
"displayName": "",
"idToken": "eyJhbGciOiJSUzI1N... a 985 char long token",
"registered": true,
"refreshToken": "AGEhc0Dq9fodOAAs_on5A... a 298 char long token",
"expiresIn": "3600"
}
So far so good. Now I take the idToken from the response and try to use it in the request headers (as the documentation says):
const getDocsFromFirestore = async () => {
const responseSignin = await getTokensBySigningIn();
const URL_GET_FIRESTORE_DATABASES = `https://firestore.googleapis.com/v1/projects/${PROJECT_ID}/databases/documents`;
const response = await fetch(URL_GET_FIRESTORE_DATABASES, {
method: "GET",
headers: {
Authorization: `Bearer ${responseSignin.idToken}`,
},
});
const responseFirestoreDB = await response.json();
console.log(responseFirestoreDB);
};
But now, if I call the getDocsFromFireStore() function, I get the following error:
{
"error": {
"code": 401,
"message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"status": "UNAUTHENTICATED"
}
}
Any help?
Note that, I have set the requirements of auth in the Firestore rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if
request.auth != null;
}
}
}
You are not specifying the database that you are looking into. Compare your API request URL with the one in the documentation:
/projects/YOUR_PROJECT_ID/databases/(default)/documents/cities/LA
^^^^^^^^^
Try changing your request URL to:
const URL_GET_FIRESTORE_DATABASES = `https://firestore.googleapis.com/v1/projects/${PROJECT_ID}/databases/(default)/documents`;
That being said, there's no such endpoint. Are you trying to list documents of a specific collection? Then try specifying the collection name:
https://firestore.googleapis.com/v1/projects/${PROJECT_ID}/databases/(default)/documents/users;
^^^^^
You can find more about REST API endpoints in the documentation.
Firebase question
My goal is to keep a user signed in a desktop app made primarily with NodeJS
User signs in
Store some kind of token to sign user in later
When user re-opens app, sign user in with that token if available
Token can expire / be invalidated
Currently, it seems Firebase relies on cookies of some sort. Is there a way to sign in a user without their email/password again?
I am currently trying to use Firebase authentication as part of my Electron app where I cannot have the Firebase authentication code be part of a renderer process which would normally have access to browser-level storage.
The ideal situation would be something like this that works:
const auth_token = await user.getIdToken();
firebase
.auth()
.signInWithCustomToken(auth_token) // But maybe some other method?
.then((user) => {
console.log(user);
})
How would you do this in a NodeJS app that has no access to browser storage such that the user does not need to continuously login?
Referencing this issue: Persist Firebase user for Node.js client application
fwiw, I don't wish to share these tokens with another application.
Electron question
A slightly different question: Can the main process in Electron store cookies that Firebase could access?
Recently read this: https://github.com/firebase/firebase-js-sdk/issues/292
My solution to this was:
Get a refresh token via email/password (long-lived ~1 yr)
Get an id_token with the refresh token (short-lived)
Get a custom token with an id_token (short-lived: ~1 hour)
Sign in with custom token
Save refresh token locally - never share it
So, something like this:
import Store from 'electron-store';
import firebase from 'firebase';
import * as request from 'request-promise';
const store = new Store({ name: 'config' });
function logout() {
const user = firebase.auth().currentUser;
store.delete('refresh_token');
if (user) {
return firebase.auth().signOut();
}
}
// https://firebase.google.com/docs/reference/rest/auth#section-create-email-password
function signup(email, password) {
return request
.post({
headers: { 'content-type': 'application/x-www-form-urlencoded' },
url: `https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=${firebase_config['apiKey']}`,
body: `email=${email}&password=${password}&returnSecureToken=true`,
json: true,
})
.then((res) => {
store.set({ refresh_token: res.refreshToken });
return login_with_id_token(res.idToken);
});
}
// Generates a refresh_token that we later use & save
async function login_with_email(email: string, password: string) {
const res = await request.post({
headers: { 'content-type': 'application/x-www-form-urlencoded' },
url: `https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${firebase_config['apiKey']}`,
body: `email=${email}&password=${password}&returnSecureToken=true`,
json: true,
});
const refresh_token = res.refreshToken;
store.set({ refresh_token });
console.log(store.path);
return login_with_refresh_token(refresh_token);
}
/**
* Needed to acquire a refresh_token
* #param refresh_token string
*/
function get_id_token(refresh_token) {
return request.post({
headers: { 'content-type': 'application/x-www-form-urlencoded' },
url: `https://securetoken.googleapis.com/v1/token?key=${firebase_config['apiKey']}`,
body: `grant_type=refresh_token&refresh_token=${refresh_token}`,
json: true,
});
}
/**
* Generates a custom token we can use to sign in given an id_token
* #param id_token string
*/
function get_custom_token(id_token) {
return request.get({
url: `https://us-central1-${firebase_config['projectId']}.cloudfunctions.net/create_custom_token?id_token=${id_token}`,
json: true,
});
}
function login_with_id_token(id_token) {
if (id_token) {
return get_custom_token(id_token).then((token) => {
// console.log(`Retrieved custom token: ${custom_token}`);
return firebase.auth().signInWithCustomToken(token);
});
}
}
/**
* If token is null, it attempts to read it from disk otherwise
* it will use the one supplied to login
* #param token string || null
*/
async function login_with_refresh_token(token = null) {
let id_token = null;
let refresh_token = token;
if (!refresh_token) {
refresh_token = store.get('refresh_token');
store.get('refresh_token', null);
// console.log('Using a cached refresh token...');
}
if (refresh_token) {
const res = await get_id_token(refresh_token);
if (res) {
id_token = res['id_token'];
return login_with_id_token(id_token);
}
}
}
// Purposely attempt to login without a refresh_token in case it's on disk
function attempt_login() {
return login_with_refresh_token(null);
}
Firebase cloud function:
exports.create_custom_token = functions.https.onRequest(async (req, res) => {
const id_token = req.query.id_token;
const user = await admin.auth().verifyIdToken(id_token);
if (user) {
const custom_token = await admin.auth().createCustomToken(user.uid);
res.setHeader('Content-Type', 'application/json');
res.status(200).send(custom_token);
} else {
res.sendStatus(500);
}
});
I have been working on a oauth2 flow for spotify by following this similar tutorial by the Firebase team for Instagram HERE
I am able to submit my credentials and return the user code and state in the url, but when I run the method to submit the code to return an auth token, the auth token that I print to console in the Firebase functions returns: Auth Token Error Not Found. Here's my workflow:
Here's the Spotify docs
FIRST, I have a function to configure my spotifyOAuth:
function spotifyOAuth2Client() {
// Spotify OAuth 2 setup
const credentials = {
client: {
id: functions.config().spotify.clientid,
secret: functions.config().spotify.clientsecret,
},
auth: {
tokenHost: 'https://accounts.spotify.com',
authorizePath: '/authorize'
},
};
return require('simple-oauth2').create(credentials);
}
I use that function in this Firebase function that is called using https://us-central1-<my project string>.cloudfunctions.net/redirect:
exports.redirect = functions.https.onRequest((req, res) => {
const oauth2 = spotifyOAuth2Client();
cookieParser()(req, res, () => {
const state = req.cookies.state || crypto.randomBytes(20).toString('hex');
console.log('Setting verification state:', state);
res.cookie('state', state.toString(), {
maxAge: 3600000,
secure: true,
httpOnly: true,
});
const redirectUri = oauth2.authorizationCode.authorizeURL({
redirect_uri: OAUTH_REDIRECT_URI,
//scope: OAUTH_SCOPES,
state: state,
});
console.log('Redirecting to:', redirectUri);
res.redirect(redirectUri);
});
});
The code above returns a url string with the proper parameters, the following code block is where my code breaks, I have another cloud function that runs after being redirected from the res.redirect(redirectUri) above. And when I try to run the getToken() method, it appears to not return anything because I hit the catch block instead? This is where I observe the Auth Token Error Not Found.
const oauth2 = spotifyOAuth2Client();
try {
return cookieParser()(req, res, async () => {
console.log('Received verification state:', req.cookies.state);
console.log('Received state:', req.query.state);
if (!req.cookies.state) {
throw new Error('State cookie not set or expired. Maybe you took too long to authorize. Please try again.');
} else if (req.cookies.state !== req.query.state) {
throw new Error('State validation failed');
}
console.log('Received auth code:', req.query.code);
console.log(OAUTH_REDIRECT_URI);
// Get the access token object (the authorization code is given from the previous step).
const tokenConfig = {
code: req.query.code,
redirect_uri: 'http://localhost:8100/popup'
};
// Save the access token
try {
const result = await oauth2.authorizationCode.getToken(tokenConfig)
const accessToken = oauth2.accessToken.create(result);
console.log('inside try');
console.log(result);
console.log(accessToken);
} catch (error) {
console.log('Access Token Error', error.message);
}
I've double checked my spotify client/secret credentials in the config, what is going wrong with this OAuth2 flow?
Resolved my issue, I was not using the correct endpoints:
const credentials = {
client: {
id: functions.config().spotify.clientid,
secret: functions.config().spotify.clientsecret,
},
auth: {
tokenHost: 'https://accounts.spotify.com',
authorizePath: '/authorize',
tokenPath: '/api/token'
},
};