I am trying to initialize a new chat using a devToken to allow client-side generated tokens by coding the following inside an async function:
let chatClient = await new StreamChat(apiKey);
await chatClient.updateAppSettings({
disable_auth_checks: true,
});
await chatClient.setUser(
{
id: 'user-0',
name: random-user-name,
},
chatClient.devToken('user-0'),
);
but am getting the error that "Both secret and user tokens are not set", despite using a devToken. The error stops the code from running once it hits
await chatClient.updateAppSettings({
disable_auth_checks: true,
});
, which is supposed to allow me to use the .devToken in place of a userToken.
So I added my secretKey to new StreamChat
let chatClient = await new StreamChat(apiKey, secretKey);
however, then I get the error "TypeError: Cannot read property 'sign' of null at JWTServerToken" right after that line runs.
And if I try to generate a userToken and replace chatClient.devToken('user-0') with userToken like this
let userToken = await chatClient.createToken("user-0");
I get the error "Error: tokens can only be created server-side using the API Secret", which is what I was trying to avoid.
Does anyone know how to correctly enable .devTokens so that the chatClient can be set without generating a token server-side?
Thanks!
This needs to be done via the dashboard. First, make sure your app is in development mode.
Then, navigate to the app's chat overview page and enable "Disable Auth Checks". Hit save.
Related
I'm trying to use the Shopify API to query all the orders of a selected Shopify store, using the private app instead of the OAUTH method. Below I have added the code, can't seem to figure out how to get it to work cause there isn't much documentation for the use of the private apps. Does anyone know how I can achieve this or has done this before? I think I maybe wrong but there maybe an error in creating the session.
Upon running the below code I get the below error:
Error: Missing adapter implementation for 'abstractRuntimeString' - make sure to import the appropriate adapter for your platform
const { shopifyApi, ApiVersion, Session, LATEST_API_VERSION } = require('#shopify/shopify-api');
const { randomUUID } = require('crypto');
const { restResources } = require('#shopify/shopify-api/rest/admin/2022-10');
const selectedStore = {
shop: "store.myshopify.com",
api_secret: "",
api_key: "",
private_admin_key: ""
};
const shopify = shopifyApi({
apiKey: selectedStore.api_key,
apiSecretKey: selectedStore.api_secret,
scopes: ['read_orders', 'read_analytics', 'read_customers'],
hostName: '<ngrok_url>',
apiVersion: LATEST_API_VERSION,
isEmbeddedApp: false,
isPrivateApp: true,
restResources
});
const session = new Session({
id: randomUUID(),
state: 'state',
shop: selectedStore.shop,
accessToken: selectedStore.private_admin_key,
isOnline: true,
})
console.log(session)
const getOrders = async () => {
const orders = await shopify.rest.Order.all({
session,
status: "all"
})
return orders
}
getOrders()
If you have a Auth Token from a private App (inside the Shopify Admin), with permissions to read orders, you make a call to the Shopify store using that token. Look up the end point you call. Formulate your call. Make a GET or POST. Nothing hard to do there. Shopify assumes you know how to make a GET request with JS. Provide the Auth Token you gave yourself, and you'll get back all the orders you asked for. You cannot skip out on learning paging etc.. but again, that is also pretty standard stuff not special to Shopify.
Stack:
Google Sign-in (Vanilla JS - client side),
Firebase Functions (ExpressJS)
Client-Side:
My Firebase function express app uses vanilla javascript on the client side. To authenticate I am making use of Firebase's Google SignIn feature client-side javascript web apps, found here.
// Firebase setup
var firebaseConfig = {
apiKey: "AIza...",
authDomain: "....firebaseapp.com",
databaseURL: "https://...-default-rtdb.firebaseio.com",
...
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE);
function postIdTokenToSessionLogin(idToken, csrfToken) {
return axios({
url: "/user/sessionLogin", < ----- endpoint code portion found below
method: "POST",
data: {
idToken: idToken,
csrfToken: csrfToken,
},
});
}
// ...
// On sign-in click
var provider = new firebase.auth.GoogleAuthProvider();
firebase.auth()
.signInWithPopup(provider)
.then(async value => {
const idToken = value.credential.idToken;
const csrfToken = getCookie('_csrf');
return postIdTokenToSessionLogin(idToken, csrfToken);
}).then(value => {
window.location.assign("/user/dashboard")
}).catch((error) => {
alert(error.message);
});
Note I am using value.credential.idToken (most sources imply to use this, but haven't found an example saying use this specifically)
Directly after calling signInWithPopup, a new account is created in my Firebase Console Authentication matching the gmail account that was just signed in.
Server-side:
Once I authenticate, I create an axios request passing in the {user}.credential.idToken and following the server-side setup here (ignoring the CSRF - this just doesn't want to work).
In creating the session, I use the following code in my firebase functions express app, the endpoint which is router.post('/sessionLogin', (req, res) => (part of /user route prefix):
// Set session expiration to 5 days.
const expiresIn = 60 * 60 * 24 * 5 * 1000;
const idToken = req.body.idToken.toString(); // eyJhbGciOiJSUzI1NiIsImt...[936]
admin
.auth()
.createSessionCookie(idToken, {expiresIn}) < ----------- Problem line
.then((sessionCookie) => {
// Set cookie policy for session cookie.
const options = {maxAge: expiresIn, httpOnly: true, secure: true};
res.cookie('session', sessionCookie, options);
res.end(JSON.stringify({status: 'success'}));
}).catch((error) => {
console.error(error);
res.status(401).send('UNAUTHORIZED REQUEST!');
});
On the createSessionCookie call, I get the following error & stack trace:
Error: There is no user record corresponding to the provided identifier.
at FirebaseAuthError.FirebaseError [as constructor] (C:\Users\CybeX\Bootstrap Studio Projects\future-design\functions\node_modules\firebase-admin\lib\utils\error.js:44:28)
at FirebaseAuthError.PrefixedFirebaseError [as constructor] (C:\Users\CybeX\Bootstrap Studio Projects\future-design\functions\node_modules\firebase-admin\lib\utils\error.js:90:28)
at new FirebaseAuthError (C:\Users\CybeX\Bootstrap Studio Projects\future-design\functions\node_modules\firebase-admin\lib\utils\error.js:149:16)
at Function.FirebaseAuthError.fromServerError (C:\Users\CybeX\Bootstrap Studio Projects\future-design\functions\node_modules\firebase-admin\lib\utils\error.js:188:16)
at C:\Users\CybeX\Bootstrap Studio Projects\future-design\functions\node_modules\firebase-admin\lib\auth\auth-api-request.js:1570:49
at processTicksAndRejections (internal/process/task_queues.js:93:5)
This is part of the sign-in flow with a existing Gmail account.
What is causing this?
After many hours of searching, Googling - I have seen the light.
For some additional context, this error featured heavily in my struggle "Firebase ID token has invalid signature." - I will get to that in a second.
Further, another issue I also faced was using a local auth emulator for web client-side (javascript), see this for setup.
TL;DR to solve the immediate problem
Client-side remained largely the same, however the documentation provided by Firebase was inaccurate/misleading - thanks to this post, I found the solution. Thus, it follows...
Which is the ID Token? (Client-side):
The examples from here (to allow signInWithPopup), the response (if successful) results in
...
.signInWithPopup(provider)
.then((result) => {
/** #type {firebase.auth.OAuthCredential} */
var credential = result.credential;
// This gives you a Google Access Token. You can use it to access the Google API.
var token = credential.accessToken;
// The signed-in user info.
var user = result.user;
// ...
})
Looking for an idToken, I found one using result.credential.idToken but no where on the internet on if this was infact the correct token to use.
I ran into this error using the provided idToken above:
Firebase ID token has incorrect "aud" (audience) claim. Expected
"[insert your **projectId**]" but got
"59895519979-2l78aklb7cdqlth0eob751mdm67kt301.apps.googleusercontent.com".
Make sure the ID token comes from the same Firebase project as the
service account used to authenticate this SDK.
Trying other tokens like result.credential.accessToken responded with various verification errors - what to do?
Mention earlier, this solution on Github suggested to use firebase.auth().currentUser.getIdToken() AFTER you have signed in. An example (building on my previous code) is to do the following:
...
.signInWithPopup(provider)
.then((result) => {
// current user is now valid and not null
firebase.auth().currentUser.getIdToken().then(idToken => {
// send this ID token to your server
const csrfToken = getCookie('_csrf');
return postIdTokenToSessionLogin(idToken, csrfToken);
})
})
At this point, you can verify your token and createSessionCookies to your heart's desire.
BUT, a secondary issue I unknowingly created for myself using the Authentication Emulator.
To setup for client-side use:
var auth = firebase.auth();
auth.useEmulator("http://localhost:9099");
To setup for hosting your firebase functions app (assuming you are using this with e.g. nodejs + express, see this for setup, ask in comments, can provide more details if needed)
Using Authentication Emulator caused the following errors AFTER using the above mentioned "fix". Thus, DO NOT RUN the local authentication emulator (with Google sign-in of a valid Google account) as you will consistently get.
Firebase ID token has invalid signature. See
https://firebase.google.com/docs/auth/admin/verify-id-tokens for
details on how to retrieve an ID token
You can use all your local emulators, but (so far in my experience) you will need to use an online authenticator.
When users sign up, they use Phone Auth. After using the app for a while, they are advised to link an (email & password) to their existing account.
The linking process fails because of the error (auth/requires-recent-login.) My code follows.
// The following generates the error: [auth/requires-recent-login] This operation is sensitive and requires recent authentication. Log in again before retrying this request.
const emailCredential = firebase.auth.EmailAuthProvider.credential(state.email, state.password);
const newCredential = await firebase.auth().currentUser.linkWithCredential(emailCredential);
To fix this error, I understand that I need to call reauthenticateWithCredential() before linking. However, I don't want to ask the user to log in again (receive & enter a verification code.) Is this at all possible?
I tried passing the result of currentUser.getIdToken(true) to PhoneAuthProvider.credential() I am not sure if this is right. Anyway, it generated an error (Cannot create PhoneAuthCredntial without either verificationProof, sessionInfo, temporary proof, or enrollment ID.).
My code follows.
// The following works:
const accessToken = await firebase.auth().currentUser.getIdToken(true);
// The following works:
const currentCredential = firebase.auth.PhoneAuthProvider.credential(accessToken);
// The following generates the error: Cannot create PhoneAuthCredential without either verificationProof, sessionInfo, temporary proof, or enrollment ID.
const abc = await firebase.auth().currentUser.reauthenticateWithCredential(currentCredential);
// The following is never reached:
const emailCredential = firebase.auth.EmailAuthProvider.credential(state.email, state.password);
const newCredential = await firebase.auth().currentUser.linkWithCredential(emailCredential);
Thank you for your effort and time to help me...
Important Information:
firebase.auth().currentUser.reauthenticateWithCredential(credential) requires the attribute credential
For users, who logged in using a Phone Number, I could not find a way to get this credential when required. By the way, it is possible to get it for users, who logged in using other providers, e.g. Facebook.
However, for users, who log in using a phone number, it is possible to get this credential during the login process. Check https://firebase.google.com/docs/auth/web/phone-auth.
So, I decided to save the credential for the user on their device during the login process. I am using Redux and Redux-Persist for that.
My code after fixing it.
// This is an extract from the login script:
firebase.auth().signInWithPhoneNumber(phoneNo)
.then(confirmResult => {
dispatch({ type: "PhoneNo_accepted", payload: { confirmResult: confirmResult } });
})
.catch(error => {
dispatch({ type: "display_message", payload: { messageText: `Phone Number Error: ${error.message}` } });
});
// Change#1. The following statement is a NEW step, which I added to get the credential during the login process.
const credential = firebase.auth.PhoneAuthProvider.credential(state.confirmResult.verificationId, state.codeInput);
state.confirmResult.confirm(state.codeInput)
.then( (user) => {
// Change#2. The following function would save the credential to the app's state, e.g. using Redux
_onAuthComplete(user, credential);
})
.catch( error => {
dispatch({ type: "display_message", payload: { messageText: `Verification Code Error: ${error.message}` } });
});
// // //
// This is an extract from the linking script:
// Change#3. props.credential is the credential, which was saved to the app's state.
await firebase.auth().currentUser.reauthenticateWithCredential(props.credential);
const emailCredential = firebase.auth.EmailAuthProvider.credential(state.email, state.password);
const newCredential = await firebase.auth().currentUser.linkWithCredential(emailCredential);
Clearly by the negative score, I haven't provided enough information - sorry about that. However, perhaps add comments to explain why rather than just marking it down?
2nd attempt at a description:
I would like to be able to connect to Spotify's web API interface (https://developer.spotify.com/web-api/) on a headless embedded platform (Arm based simple MCU with WiFi). The username and password would be hardcoded into the system, probably added at setup time with the help of a mobile device (providing a temporary user interface).
I want to be able to add tracks to a playlist, which requires an authentication token. Spotify's usual flow requires the embedded platform to host their webpage login, as described here (https://developer.spotify.com/web-api/authorization-guide/).
Is this possible to authenticate without the webpage?
I have seen here (https://developer.spotify.com/technologies/spotify-ios-sdk/token-swap-refresh/) that Spotify recommend mobile apps use a remote server to handle refreshing of tokens - perhaps that's a route?
Any pointers would be appreciated.
I don't think it is bad question. I am also working on a headless player that runs on a local network which makes the authorization flow a bit awkward. So this is not much of an answer, but let me explain how it can be done.
Your headless system needs to have a web interface that can redirect to the spotify authorization url and handle the callback. The problem is that you have to register the callback url on your spotify app. Say you register http://server1/spotify/auth/callback. Now the server1 needs to be accessible from the device doing the authorization, f.ex by adding it to /etc/hosts.
The good news is that refresh can be done without user intervention, so if you store the access token the user will only need to do this one time after installing.
I know that this is really late, but for anyone having the same issue...
I am working on something similar was mentioned above so I'll share what I know. I am creating a music player that could act as another device on my Spotify (using: https://developer.spotify.com/documentation/web-playback-sdk/) account as well be controlled by my custom webpage.
I have 3 parts to this: backend server, the SDK player webpage (for me: http://localhost:8080/#/pup/player), the frontend UI webpage
(all the code snippets are a part of a class)
The only way I was able to get it running was like so:
Start the backend server and initialize puppeteer
async initPup(){
this.browser = await puppeteer.launch({
headless: false, // This is important, because spotify SDK doesn't create the device when using headless
devtools: true,
executablePath: "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe", //I also have to use Chrome and not Chromium, because Chromium is missing support for EME keySystems (yes, I've tried bruteforcing chromium versions or getting Firefox to work using createBrowserFetcher())
ignoreDefaultArgs: ['--mute-audio'],
args: ['--autoplay-policy=no-user-gesture-required']
});
this.page = (await this.browser.pages())[0]; // create one page
if(this.page == undefined){
this.page = await this.browser.newPage();
}
this.pup_ready = true;
console.log(await this.page.browser().version())
}
Open your SDK player page with puppeteer and pass the ClientID and ClientSecret of your Spotify project (https://developer.spotify.com/dashboard/):
async openPlayer(){
// const player_page = "http://localhost:8080/#/pup/player"
if(this.pup_ready){
await this.page.goto(player_page + "/?&cid=" + this.client_id + "&csec=" + this.client_secret);
}
}
On the SDK player webpage save the cid and csec URL params to LocalStorage. This should be done when no ULR parameter named "code" has been given, because that's the authorizations code which will be generated in the next step.
Something like:
var auth_code = url_params_array.find(x=>x.param.includes("code")); // try to get the auth code
var c_id = url_params_array.find(x=>x.param.includes("cid")); //get cid
var c_sec = url_params_array.find(x=>x.param.includes("csec")); //get csec
var token = undefined;
if(auth_code == undefined){ // the auth code is not defined yet and it has to be created
//SAVING CLIENT ID and CLIENT SECRET
c_id = c_id.value;
c_sec = c_sec.value;
window.localStorage.setItem("__cid", c_id)
window.localStorage.setItem("__csec", c_sec)
//GETTING THE AUTH CODE
var scope = "streaming \
user-read-email \
user-read-private"
var state = "";
var auth_query_parameters = new URLSearchParams({
response_type: "code",
client_id: c_id,
scope: scope,
redirect_uri: "http://localhost:8080/#/pup/player/",
state: state
})
window.open('https://accounts.spotify.com/authorize/?' + auth_query_parameters.toString()); // tak the puppeteer to the spotify login page
}
Login on the spotify page using your credential to create the auth token. I had to use https://www.npmjs.com/package/puppeteer-extra-plugin-stealth to bypass CAPTCHAS
async spotifyLogin(mail="<YOUR_SPOTIFY_MAIL>", pass = "<YOUR_SPOTIFY_PASSWORD") {
var p = this.page = (await this.browser.pages())[1] // get the newly opened page with the spotify
//await p.waitForNavigation({waitUntil: 'networkidle2'})
await p.focus("#login-username"); // put in the credentials
await p.keyboard.type(mail);
await p.focus("#login-password");
await p.keyboard.type(pass);
await p.$eval("#login-button", el => el.click());
(await this.browser.pages())[0].close(); // close the old SDK page
await sleep(1000) // wait to be redirected back to your SDK page
//
this.page = (await this.browser.pages())[0];
this.auth_code = await this.page.evaluate( (varName) => window.localStorage.getItem(varName), ["__auth"] ) // here is ave the auth token as a property of the class instance as well
}
Once you're redirected to SDK page again you already have cid and csec and now also the auth token.
if(auth_code == undefined)
//... (this is already in step 3)
}else{
// GETTING CID and C SECRET AGAIN
c_id = window.localStorage.getItem("__cid")
c_sec = window.localStorage.getItem("__csec")
// SAVING THE AUTH CODE
auth_code = auth_code.value;
window.localStorage.setItem("__auth", auth_code)
}
Generate a token on the backend.
async genToken():Promise<void>{
//Pretty much coppied from: https://developer.spotify.com/documentation/web-playback-sdk/guide/
var authOptions = {
url: 'https://accounts.spotify.com/api/token',
headers: {
'Authorization': 'Basic ' + (Buffer.from(this.client_id + ':' + this.client_secret).toString("base64"))
},
form: {
code: this.auth_code,
redirect_uri: "http://localhost:8080/#/pup/player/",
grant_type: 'authorization_code'
},
json: true
};
var token;
var refresh_token;
await request.post(authOptions, function(error, response, body) { // also get the refresh token
if (!error && response.statusCode === 200) {
token = body.access_token;
refresh_token = body.refresh_token;
}
});
while (!token && !refresh_token){ // wait for both of them
await sleep(100)
}
this.token = token; // save them in the class instance properties
this.refresh_token = refresh_token;
}
Lastly the puppeteer fills in a html field with the token generated in step 6 on the SDK site and presses a button to start the SDK player.
// this function gets called after the button gets pressed
async function main(){
console.log(window.localStorage.getItem("__cid")) // print out all the data
console.log(window.localStorage.getItem("__csec"))
console.log(window.localStorage.getItem("__auth"))
console.log(getToken())
const player = new Spotify.Player({ // start the sporify player
name: 'Home Spotify Player',
getOAuthToken: cb => cb(getToken())
});
player.connect().then(()=>{ // connect the player
console.log(player)
});
window.player = player;
}
function getToken(){
return document.getElementById("token_input").value;
}
You are done. Next step for me at least was communicating using another UI page to the backend puppeteer to control the SDK page (play/pause/skip etc.) This process is pretty "hacky" and not pretty at all but if you just have a little personal project it should do the job fine.
If anyone would be interested in the whole code I might even upload it somewhere, but I think this read is long-enough and overly detailed anyway.
The proper way for this would be to use the device authorization grant flow - Spotify does this already for its TV applications, but they seem to block other applications from using it. It is possible to find clientIds online that are working with this, but it is not supported by Spotify.
I explained how this works and requested that they enable it in a supported way for custom applications in this feature request - please upvote the idea there if you find it useful.
That said, it is also possible to implement your own device authorization grant flow by hosting an extra server between your device and Spotify. That server should
host an authorize and a token API endpoint
host a user-facing page where the user can enter the user code
a callback page for Spotify to redirect the user after login
I believe this is how https://github.com/antscode/MacAuth implements it:
When the device calls the authorize, the server should generate a record containing the device_code and user_code and send them back in the response. The server should keep the record for later.
When the user enters the user_code in the user-facing page, the server should redirect the user to Spotify to login, and after login the user should be redirected to the server's callback page. At that moment the server can fetch credentials from Spotify's token endpoint using the data it received in the callback. The server should store the credentials it received in the record of the user_code.
The device can poll the server using the device_code for the availability of the tokens using the token endpoint.
I've created service account with domain wide delegation and its scopes (in Admin console and Developer console) as described in documentation. I've been trying this for a week now and I am stuck. This is my code:
const google = require('googleapis');
const gmail = google.gmail('v1');
const directory = google.admin('directory_v1');
const scopes = [
'https://www.googleapis.com/auth/gmail.readonly',
'https://www.googleapis.com/auth/admin.directory.user.readonly'
];
const key = require('./service_key.json');
var authClient = new google.auth.JWT(
key.client_email,
key,
key.private_key,
scopes,
"kruno#example.com"
);
authClient.authorize(function(err, tokens){
if (err) {
console.log(err);
return;
}
// API call methods here...
});
I get this error:
Error: unauthorized_client
I am unable to understand:
Is this proper technique for calling Google API methods from server-side scripts without any user interaction? (under domain only)
How do service account and actual user account communicate this way?
I heard about callback URI, am I missing it?
I think you are missing the final step which is giving access to your application in the control panel of your domain.
You can follow doc properly to activate it with your application
https://developers.google.com/+/domains/authentication/delegation
Also you can start with your first call step here
https://developers.google.com/adwords/api/docs/guides/first-api-call