I am trying to implement account linking in google actions. I've chosen Google and OAuth with Implicit linking type. In the Authorization URL I am validating the request and redirecting to google's oauth handler. Here is the sample code,
#Post('/google/actions/authorize')
public async authorizeGoogle(
#Req() request: Request,
#Res() response: Response,
#Body() authorizeRequest: DAuthorizeRequest,
) {
// tempToken is stored in cookie after login.
const tempToken = request.cookies['temp-token'];
if (!tempToken) {
throw new UnauthorizedException();
}
let token: DTemporaryToken;
try {
token = await this.jwtService.verifyAsync<DTemporaryToken>(tempToken);
} catch (err) {
throw new UnauthorizedException();
}
// validate request parameters are as it should be.
const valid = this.authService.validateGoogleOauthRequest(
token,
authorizeRequest,
);
if (!valid) {
throw new UnauthorizedException();
}
const user: User = await this.userService.findById(token.user_id);
const accessToken = await this.authService.generateAccessTokenForGoogle(
user,
);
const redirectUri = `${
authorizeRequest.redirect_uri
}?access_token=${accessToken}&error=${false}&token_type=bearer&state=${
authorizeRequest.state
}`;
response.redirect(redirectUri);
}
After the redirect I get this error,
Sorry, something went wrong, so I couldn't sign you in. But you can
try again later.
This is the dialogflow code
dialogFlowApp.intent(
'Default Welcome Intent',
async (conv: DialogflowConversation) => {
conv.ask(new SignIn('to access your data'));
},
);
dialogFlowApp.intent('sign_in', (conv, params, signIn) => {
console.log('SIGN IN', signIn)
conv.ask('how are you?');
})
And the console log for the signIn value is
SIGN IN { '#type':
'type.googleapis.com/google.actions.v2.SignInValue', status: 'ERROR' }
That's it, I can't figure out what is it that is going wrong. There is no descriptive enough error that should explain where this are going wrong.
it was a silly mistake on my part. The issue was in the redirect url, instead of sending the access_token and other parameters as url fragment, I was sending them as query parameter. So changing the access token generation to this fixed the issue.
const redirectUri = `${
authorizeRequest.redirect_uri
}#access_token=${accessToken}&error=${false}&token_type=bearer&state=${
authorizeRequest.state
}`;
Though, I still think the error reporting should be more comprehensive from google's end. It was a silly mistake which should have taken 10 seconds to fix instead of hours if the error was something more meaningful than Something went wrong
Related
I've integrated a user authentication and it already works very well. (Authenticates and returns a JWT in cookie.)
But for some reasons AFTER I logged in with my user and I want to establish a connection to the database I get a 403. The request itself isnt even protected. No verification if user is logged in or anything. Its a public request.
The funny part is, if I restart the node express application the exact same request goes through.
So it seems that anything within the process.
My login function:
const login = async (username, password) => {
try {
const response = await connection.auth(username, password)
if (!response.ok) {
return [401, null]
}
const token = jwt.sign({ sub: username }, secret, { expiresIn: '1h' })
return [null, token]
} catch (error) {
return [error, null]
}
}
This one gets called right after and I run into the catch block
try {
const conn = await nano('http://admin:password#127.0.0.1:5984')
const db = await conn.use('test')
[…]
} catch (error) {
// I LAND HERE
console.error(error)
return [error, null]
}
}
Okay I figured it out myself.
Looks like 403 was sent because it overrides your current session. What I did was basically authenticate again right after "login" was successful but this time I used admin and password again. Kinda ugly but it works.
const response = await connection.auth(username, password)
if (!response.ok) {
return [401, null]
}
await connection.auth('admin', 'password') // < server credentials
Sorry if this isn't worded correctly I am very new to Node and to GoogleAPI.
I have an app with a button to login/authorize GoogleAPI through OAuth 2.0 so that I can add events to the calendar.
This all works perfectly fine. I can successfully login and add events to the calendar. I will add my code for this at the end in case it is relevant.
My trouble is that I would like to run a check on the GET request of the page that has the button to check if the GoogleAPI is connected/authorized so that I can show the button and give a warning to log in/authorize if it is not, and to hide the button if it is.
For the life of me, I have not been able to figure this out, I've been stuck on it for days now and have tried many many different approaches.
I have read the documentation, but it is not clear and I don't understand it too well.
I thought maybe it has something to do with validating an ID token? But I'm not sure, and couldn't work out how to do that.
If you have any advice it would be greatly appreciated!!!
Here is the code for the initial authorization that works fine in case it is helpful:
const oAuth2Client = new google.auth.OAuth2(
CLIENT_ID,
CLIENT_SECRET,
REDIRECT_URL,
);
exports.googleAuthCode = (req, res) => {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});
console.log('authURL: ',authUrl)
res.redirect(authUrl);
}
exports.googleRedirect = async (req, res) => {
const code = req.query.code;
const {tokens} = await oAuth2Client.getToken(code)
oAuth2Client.setCredentials(tokens);
req.session.tokens = tokens;
res.redirect('/jobs?message=Success')
}
In response to #DaImTo it does not request authorizaiton if it is not already authorized it simply throws the error An error occurred: Error: No access, refresh token, API key or refresh handler callback is set. and refreshes the page.
Here is the code for the GET request that adds the events, maybe I have set it up wrong?
exports.scheduleEvent = async (req, res) => {
const date = req.query['date'];
let events = await Events.findOne({}).sort({_id: -1});
events = events.events
events.forEach(async (event) => {
try {
const result = await calendar.events.insert({
auth: oAuth2Client,
calendarId: 'primary',
resource: event,
});
console.log(`Event created: ${result.data.htmlLink}`);
} catch (err) {
console.error(`An error occurred: ${err}`);
}
});
res.send({
msg: "Done"
})
}
I have to switch from 'Google Sign In' to 'Sign In With Google' (SIWG).
I use passport.js with passport-google-oidc. SIWG works, I ask for the scopes openid, profile and email, and I get it.
But SIWG does not give me any access token as it did before Google Sign In. I need this access token to continue with 'Subscribe With Google' (SWG).
Is there any scope that gives it to me?
As I didn't get from SIWG any access token but at least a 'code' I tried to work with this code.
With google-auth-library and the code, I tried to get an access token. With this library, I use the same id, secret and callback url as with SIWG.
But oAuth2Client.getToken(code) only gives me {"error": "invalid_grant", "error_description": "Bad Request"}
Any idea how to make the request go through ok?
` // 1. Defining strategy
const GoogleStrategy = require('passport-google-oidc');
const Config_Account_Passport = {
// "google" will refer to strategy passport-google-oidc SIWG
Google : new GoogleStrategy({
clientID : '6...MyClientID0c...apps.googleusercontent.com',
clientSecret : 'a...MyClientSecret...',
callbackURL: "https://...UrlOfMyServer.../google/auth",
passReqToCallback : true
}, async function(req, issuer, profile, done) {
// verified function
try {
// get code
const code = req.query.code;
// ...
// get data for login of user. Store data in database if new user.
const email = profile.emails[0].value;
const oauth_id = profile.id;
// Look for user in database. If new store. In Both cases: Having a user object.
//...
// AND now to entitlements (part 4 below)
await checkEntitlements(user, code);
// following never executed because checkEntitlements fails -> goes to catch
// Everything is fine
return done(null, user)
} catch(e) {
return done(true, false); // error and no user
}
})
}
module.exports = Config_Account_Passport;
// 2. Applying strategy
//...
passport.authenticate('google', { scope: ['openid','profile','email','https://www.googleapis.com/auth/subscribewithgoogle.publications.readonly'], accessType: 'offline'});
// 3. Google calls callbackURL -> Successful verified
//...
function(req, res, next) {
// in callback from google
passport.authenticate('google', {}, function (err, user) {
// verified done
if (err) {
// done(true, ...) was called
// user is stays NOT logged id
return res.redirect('/user/login');
}
return res.redirect('/user/loggedin');
})
}
// 4. Check entitlements
// First get access token from code
const {OAuth2Client} = require('google-auth-library');
function getAccessToken(code) {
return new Promise(async (resolve, reject) => {
try {
const oAuth2Client = new OAuth2Client(
clientID : '6...MyClientID0c...apps.googleusercontent.com',
clientSecret : 'a...MyClientSecret...',
callbackURL: "https://...UrlOfMyServer.../google/auth",
);
// Generate the url that will be used for the consent dialog.
await oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: ['https://www.googleapis.com/auth/subscribewithgoogle.publications.readonly'],
});
const {tokens} = await oAuth2Client.getToken(code);
// following never executed because getToken fails -> goes to catch
// "error": "invalid_grant"
//...get access token from tokens
resolve (token);
} catch (e){
return reject(e);
}
})
}
const Payment_SwG = require('./../modules/Payment/SwG');
// Then Get Entitlement with access token
async function checkEntitlements(user, code) {
return new Promise(async (resolve, reject) => {
try {
// first get access token by using code
const accesstoken = await getAccessToken(code);
// following never executed because getAccessToken fails -> goes to catch
// get entitlements by access token
const entitlements = await Payment_SwG.checkEntitlements(accesstoken);
//...
} catch(e) {
return reject(e);
}
})
}`
I tried different scopes. I examined all parameters I got back from SIWG, looking out for an access token.
The Scopes you see in the program code are accepted by SIWG. Execution stops after successful SIWG when I try to get an access token.
I've implemented "Sign in with Apple" on my site. When I try it on my phone, it redirects me to a blank white page with the same URL as the redirect_uri I've configured.
I can't find any info on why this is happening. What's a possible fix?
UPDATE
It seems as if Apple JS SDK is creating a FORM HTML DOM element, sets the POST URL of the FORM to point to the redirect_uri, and finally programmatically clicks form.submit(). This for some reason causes the page to navigate to the redirect_uri and show the POST response as a new page.
I figured this out by tracking the Apple JS SDK in the debugger.
Here is my code
//---- Frontend ----
AppleID.auth.init({
clientId : '<client_id>',
scope : 'email',
redirectURI : 'mySite.com/apple_auth',
state : 'origin:web',
nonce : Date.now(),
//usePopup : true //not using this one. When false or undefined, Apple will make a POST request to the defined redirect_uri
})
// Listen for authorization success.
document.addEventListener('AppleIDSignInOnSuccess', (event) => {
// Handle successful response.
console.log(event.detail.data);
});
// Listen for authorization failures.
document.addEventListener('AppleIDSignInOnFailure', (event) => {
// Handle error.
console.log(event.detail.error);
});
//...
myButton.onClick = ()=>{
try {
var res = await AppleID.auth.signIn()
} catch(err) {
var x = 0
}
}
//---- Backend ----
var appleSignin = require("apple-signin-auth")
app.express.post('/apple_auth', async (req, res)=>{
var body = req.body
try {
const appleRes = await appleSignin.verifyIdToken(
body.id_token, // We need to pass the token that we wish to decode.
{
audience: '<client_id', // client id - The same one we used on the frontend, this is the secret key used for encoding and decoding the token.
ignoreExpiration: true, // Token will not expire unless you manually do so.
}
)
//do something with the Apple response
} catch (err) {
// Token is not verified
console.error(err)
}
})
From the documentation...
The HTTP body contains the result parameters with a content-type of application/x-www-form-urlencoded.
Make sure you've configured the urlencoded() body-parsing middleware in your Express app.
app.use(express.urlencoded());
Make sure you check for errors and actually send a response from your /apple_auth Express route
const { code, id_token, state, user, error } = req.body;
if (error) {
return res.status(500).send(error);
}
try {
const appleRes = await appleSignin.verifyIdToken(id_token, {
audience: "<client_id>",
ignoreExpiration: true,
});
// do something with the Apple response, then send a response
res.send(appleRes.sub);
} catch (err) {
console.error(err);
res.sendStatus(500); // send a 500 response status
}
I am making a Multi-Purpose API Service and as I got the token in the URL working perfect and authorising as expected with a 200. I've been having issues with the token not authorising with curl command or superagent, as its always return a 401 error.
auth.js
const { DB } = require('../src/Routes/index.js');
module.exports = function auth(req, res, next) {
if(!req.query.apiKey) {
return res.status(401).json({"error": "401 Unauthorized", message: "API Token is missing in the query. You will need to generate a Token and put in the apiKey query."})
} else {
let check = DB.filter(k => k).map(i => i.apiToken);
console.log(check);
let t = check.some(e => e == req.query.apiKey)
if(t !== true)
return res.status(401).json({"error": "401 Unauthorized", message: "The API Key you provided is invalid. Please refer to our Docs () and Generate a Token ()"});
return next();
}
}
This is the middleware for the token, I am using this in my routers so then the token will authenticate. However, if I remove the if statement for checking if an API Token is present. It seem to fix the issue kinda but always authenticates with any key (even ones not saved in the db) and is still not properly fixed.
and an example for requesting endpoint with token on a Discord Command:
const { MessageEmbed } = require("discord.js");
const { get } = require("superagent");
exports.run = async (bot, message, args) => {
const { body } = await get(`https://example.com/generators/3000years`)
.query({ image: message.author.displayAvatarURL({ dynamic: true, size: 2048 }) })
.set("Authorization", `Bearer MY_TOKEN`);
const embed = new MessageEmbed()
.setTitle(`**3000 Years**`)
.attachFiles({ attachment: body, name: "3000years.png" })
.setImage("attachment://3000years.png")
.setColor(`#ed8a5c`);
message.channel.send(embed);
}
You can see that if I authorise with Superagent, it will not work and return a 401 Unauthorised in the console.
I would like to ask why this is doing this and if I did something wrong. Any help is appreciated.