OAuth sign in with scopes to get access to youtube fails when brand channel is chosen in consent screen. Works correctly with average main account's youtube channel.
scope: "email profile openid https://www.googleapis.com/auth/youtube"
config: {
client_id: my_client_id,
fetch_basic_profile: true,
ux_mode: 'popup',
response_type: 'permission',
access_type: 'offline',
prompt: 'consent'
}
Request https://accounts.google.com/o/oauth2/iframerpc?action=issueToken&response_type=token%20id_token&login_hint=AJDLj6JUa8yxXrhHdWRHIV0S13cANzW1y8OtI22VrYqaEC2i73av3HLVph3WpN9U_HlWSNxCsiYTB2bXOl1-DWQmT0zrdSxGa1JR03gPm3hk1GCjY9-EQsDI5aEH73UEvOOCoqynaOnr&client_id={my_real_token_here}&origin=http%3A%2F%2Flocalhost%3A4000&scope=openid%20profile%20email&ss_domain=http%3A%2F%2Flocalhost%3A4000
Failed with 403, response - { "error" : "invalid_request" }
gapi.auth2.getAuthInstance()
.signIn({ ...config, scope })
.then(res => {
const token = res.getAuthResponse().access_token
return token
})
.catch((err) => {
console.log(err) # {type: "tokenFailed", idpId: "google", error: "server_error"}
throw new Error()
})
Related
I have employed the Login with Google functionality in my React app. I am getting the jwt but there is no access token included in the jwt which I need for sending it to the backend (Laravel). On the backend I use Socialite and I want to get the user back with the access token. Right now I am verifying the user with jwt which is not working.
React Code.
const handleGoogleCallbackResponse = (response) => {
signinWithGoogle(response.credential)
}
const signinWithGoogle = async (jwt) => {
try {
const res = await axios.post("/api/users/loginwithgoogle", {jwt: jwt})
console.log("Google data from backend: ", res.data);
} catch (error) {
console.log("Error at signinWithGoogle : ", error);
}
}
useEffect(() => {
/* global google */
google.accounts.id.initialize({
client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID,
callback: handleGoogleCallbackResponse
})
google.accounts.id.renderButton(document.getElementById("google-btn"), {theme: "outline", size: "large"})
}, [])
Backend:
$user = Socialite::driver('google')->stateless()->userFromToken($request->jwt);
I am trying to get an access token for accessing the Firebase Hosting API from a Service account, as described here.
The code below does not return an access_token, but an id_token instead, which fails to authenticate when trying to use the API.
What am I doing wrong? How can I obtain an access token?
const { google } = require("googleapis");
var serviceAccount = require("../functions/src/services/serviceAccountKey.json");
async function getAccessToken() {
try {
const jwtClient = new google.auth.JWT(
serviceAccount.client_email,
null,
serviceAccount.private_key,
["firebasehosting.googleapis.com"],
null
);
const credentials = await jwtClient.authorize();
console.log(credentials);
} catch (error) {
console.log(error);
}
}
getAccessToken();
It returns a credentials object:
{
access_token: undefined,
token_type: 'Bearer',
expiry_date: undefined,
id_token: '...', // edited out
refresh_token: 'jwt-placeholder'
}
For the record, I finally got it.
My token scope was invalid: I should use https://www.googleapis.com/auth/firebase
The valid scopes are listed here
I'm trying to send an email through the gmail API from a Node.js application. I had this working, following the documentation and using the node-mailer package. However, I noticed that when we change our organizations password, the connection is no longer good (which makes sense). I'm therefore trying to authorize with a JWT instead.
The JWT is correctly generated and posted to https://oauth2.googleapis.com/token. This request then returns an access_token.
When it comes time to write and send the email, I tried to simply adapt the code that was previously working (at the time with a client_secret, client_id and redirect_uris):
const gmail = google.gmail({ version: 'v1', auth: access_token });
gmail.users.messages.send(
{
userId: 'email',
resource: {
raw: encodedMessage
}
},
(err, result) => {
if (err) {
return console.log('NODEMAILER - The API returned: ' + err);
}
console.log(
'NODEMAILER Sending email reply from server: ' + result.data
);
}
);
The API keeps returning Error: Login Required.
Does anyone know how to solve this?
EDIT
I've modified my code and autehntication to add the client_id and client_secret:
const oAuth2Client = new google.auth.OAuth2(
credentials.gmail.client_id,
credentials.gmail.client_secret,
credentials.gmail.redirect_uris[0]
);
oAuth2Client.credentials = {
access_token: access_token
};
const gmail = google.gmail({ version: 'v1', auth: oAuth2Client });
gmail.users.messages.send(
{
userId: 'email',
resource: {
raw: encodedMessage
}
},
(err, result) => {
if (err) {
return console.log('NODEMAILER - The API returned: ' + err);
}
console.log(
'NODEMAILER Sending email reply from server: ' + result.data
);
}
);
But now the error is even less precise: Error: Bad Request
Here's the final authorization code that worked for me:
var credentials = require('../../credentials');
const privKey = credentials.gmail.priv_key.private_key;
var jwtParams = {
iss: credentials.gmail.priv_key.client_email,
scope: 'https://www.googleapis.com/auth/gmail.send',
aud: 'https://oauth2.googleapis.com/token',
exp: Math.floor(new Date().getTime() / 1000 + 120),
iat: Math.floor(new Date().getTime() / 1000),
sub: [INSERT EMAIL THAT WILL BE SENDING (not the service email, the one that has granted delegated access to the service account)]
};
var gmail_token = jwt.sign(jwtParams, privKey, {
algorithm: 'RS256'
});
var params = {
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: gmail_token
};
var params_string = querystring.stringify(params);
axios({
method: 'post',
url: 'https://oauth2.googleapis.com/token',
data: params_string,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}).then(response => {
let mail = new mailComposer({
to: [ARRAY OF RECIPIENTS],
text: [MESSAGE CONTENT],
subject: subject,
textEncoding: 'base64'
});
mail.compile().build((err, msg) => {
if (err) {
return console.log('Error compiling mail: ' + err);
}
const encodedMessage = Buffer.from(msg)
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
sendMail(encodedMessage, response.data.access_token, credentials);
});
});
So that code segment above uses a private key to create a JSON Web Token (JWT), where: iss is the service account to be used, scope is the endpoint of the gmail API being accessed (this must be preauthorized), aud is the google API oAuth2 endpoint, exp is the expiration time, iat is the time created and sub is the email the service account is acting for.
The token is then signed and a POST request is made to the Google oAuth2 endpoint. On success, I use the mailComposer component of NodeMailer to build the email, with an array of recipients, a message, a subject and an encoding. That message is then encoded.
And here's my sendMail() function:
const oAuth2Client = new google.auth.OAuth2(
credentials.gmail.client_id,
credentials.gmail.client_secret,
credentials.gmail.redirect_uris[0]
);
oAuth2Client.credentials = {
access_token: access_token
};
const gmail = google.gmail({ version: 'v1', auth: oAuth2Client });
gmail.users.messages.send(
{
userId: 'me',
resource: {
raw: encodedMessage
}
},
(err, result) => {
if (err) {
return console.log('NODEMAILER - The API returned: ' + err);
}
console.log(
'NODEMAILER Sending email reply from server: ' + result.data
);
}
);
In this function, I am creating a new googleapis OAuth2 object using the credentials of the service account (here stored in an external file for added security). I then pass in the access_token (generated in the auth script with the JWT). The message is then sent.
Pay attention to the userId: 'me' in the sendMail() function, this was critical for me.
This is the way I was able to only use googleapis package instead of axios + googleapis with your service account. You will need domain wide authority for this account with the scope used below associated with it. Follow this to do that https://support.google.com/a/answer/162106?hl=en
You can also use the mailComposer example up above to create the email. keys is the service_credentials.json file you get when making this service account
const { google } = require('googleapis');
const scope = ["https://www.googleapis.com/auth/gmail.send"];
const client = new google.auth.JWT({
email: keys.client_email,
key: keys.private_key,
scopes: scope,
subject: "emailToSendFrom#something.com",
});
await client.authorize();
const gmail = google.gmail({ version: 'v1', auth: client});
const subject = '🤘 Hello 🤘';
const utf8Subject = `=?utf-8?B?${Buffer.from(subject).toString('base64')}?=`;
const messageParts = [
'From: Someone <emailToSendFrom#something.com>',//same email as above
'To: Someone <whoever#whoever.com>',
'Content-Type: text/html; charset=utf-8',
'MIME-Version: 1.0',
`Subject: ${utf8Subject}`,
'',
'This is a message just to say hello.',
'So... <b>Hello!</b> 🤘❤️😎',
];
const message = messageParts.join('\n');
// The body needs to be base64url encoded.
const encodedMessage = Buffer.from(message)
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
const res = await gmail.users.messages.send({
userId: 'me',
requestBody: {
raw: encodedMessage,
},
});
console.log(res.data);
I am creating a backend which relies in Express and GraphQL which will serve clients apps (android and react).
I have been following this article on how to nail social authentication in GraphQL using passport.js.
The article uses passport-google-token strategy and is based on Apollo-server but personally I prefer to use express-graphql.
After setting my android app to use google auth and try to send mutation to server I get this error
Google info: { InternalOAuthError: failed to fetch user profile
at E:\_Projects\myProject\myProject-backend\node_modules\passport-google-token\lib\passport-google-token\strategy.js:114:28
at passBackControl (E:\_Projects\myProject\myProject-backend\node_modules\oauth\lib\oauth2.js:132:9)
at IncomingMessage.<anonymous> (E:\_Projects\myProject\myProject-backend\node_modules\oauth\lib\oauth2.js:157:7)
at IncomingMessage.emit (events.js:194:15)
at endReadableNT (_stream_readable.js:1125:12)
at process._tickCallback (internal/process/next_tick.js:63:19)
name: 'InternalOAuthError',
message: 'failed to fetch user profile',
oauthError:
{ statusCode: 401,
data:
'{\n "error": {\n "code": 401,\n "message": "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",\n "status": "UNAUTHENTICATED"\n }\n}\n' } }
I believe token I pass maybe does not reach where it supposed but I cant figure out how to solve.
I have tested my token here https://oauth2.googleapis.com/tokeninfo?id_token=MyToken and they are working correctly.
Here is graphQL config in app.js
app.use('/graphql', graphqlHTTP((req, res) => ({
schema,
graphiql: true,
context: {req, res}
})));
Here is google auth mutation
googleAuth: {
type: authPayLoad,
args: {token: {type: new GraphQLNonNull(GraphQLString)}},
async resolve(_, {token}, {req, res}) {
req.body = {
...req.body,
access_token: token,
};
try {
const {data, info} = await authenticateGoogle(req, res);
console.log("Google data: ", data);
if (data) {
const user = await User.upsertGoogleUser(data);
if (user) {
return ({
name: user.name,
username: user.username,
token: user.generateJWT(),
});
}
}
if (info) {
console.log("Google info: ", info);
switch (info.code) {
case 'ETIMEDOUT':
return (new Error('Failed to reach Google: Try Again'));
default:
return (new Error('something went wrong'));
}
}
return (Error('server error'));
} catch (e) {
console.log("Error: ", e);
return e
}
},
},
And here is my auth controller
const passport = require('passport');
const {Strategy: GoogleTokenStrategy} = require('passport-google-token');
// GOOGLE STRATEGY
const GoogleTokenStrategyCallback = (accessToken, refreshToken, profile, done) => done(null, {
accessToken,
refreshToken,
profile,
});
passport.use(new GoogleTokenStrategy({
clientID: 'MY_CLIET_ID',
clientSecret: 'SERVER-SECRET'
}, GoogleTokenStrategyCallback));
module.exports.authenticateGoogle = (req, res) => new Promise((resolve, reject) => {
passport.authenticate('google-token', {session: false}, (err, data, info) => {
if (err) reject(err);
resolve({data, info});
})(req, res);
});
I expected when client app submit mutation with token as arg the request will be sent to google and returns user data. How do I solve this.
passport-google-token is archived and seems deprecated to me. Why don't you try passport-token-google.
It can be used in similar way.
passport.use(new GoogleStrategy({
clientID: keys.google.oauthClientID,
clientSecret: keys.google.oauthClientSecret
},
function (accessToken, refreshToken, profile, done) {
return done(null, {
accessToken,
refreshToken,
profile,
});
}
));
module.exports.authenticate = (req, res) => new Promise((resolve, reject) => {
passport.authenticate('google-token', {session: false}, (err, data, info) => {
if (err) reject(err);
resolve({data, info});
})(req, res);
});
Hope this helps.
Seems like uploading videos to Youtube via its API is restricted to user explicit authorization, and all points that this is only possible via OAuth2 instead of FWT.
Using the V3 API I'm getting a 401 response code, however I can perform read-only operations, like fetch playlists etc...
For server-to-server tasks automation OAuth is not the best solution.
Here's is the test code using node.js:
var fs = require('fs')
var google = require('googleapis')
var youtube = google.youtube('v3')
var authClient = new google.auth.JWT(
'service-account-email#developer.gserviceaccount.com',
'path/to/key.pem',
null,
['https://www.googleapis.com/auth/youtube', 'https://www.googleapis.com/auth/youtube.upload'])
authClient.authorize(function (err, tokens) {
if (err) {
return console.log(err)
}
google.options({ auth: authClient })
youtube.videos.insert({
media: {
body: fs.createReadStream('video.mp4')
},
autoLevels: true,
part: 'status,snippet',
mediaType: 'video/mp4',
resource: {
snippet: {
title: 'test video',
description: 'This is a test video uploaded from the YouTube API'
},
status: {
privacyStatus: 'public'
}
}
}, function (err, res) {
if (err) {
return console.error(err)
}
console.log('Upload done!')
})
})
Google API node.js client original example:
https://github.com/google/google-api-nodejs-client/blob/master/examples/jwt.js