After logged in by the authorization function of the react-native-app-auth lib I couldn't log in with another account until the token expired, that's because the user's session continues to be used.
OBS: I already tried to clear cookies with #react-native-cookies/cookies
I already tried using revoke, but even if the token is revoked, the user session remains in the webview
The closest to a solution I found was using the authorize function passing a logout url to the WEB application
Tried opening my own webview but logout didn't work
About the server:
Devise v4.7.3 + doorkeeper v.5.4.0 is used
The backend uses Ruby 2.7.1 and Rails 6.0.3.3
App:
React Native v0.66.3
react-native-app-auth 6.0.1
const config = {
issuer: API_URL,
clientId: CLIENT_ID,
clientSecret: CLIENT_SECRET,
redirectUrl: CALLBACK_LOGIN,
} as AuthConfiguration
export const login = async () => {
try {
const result = await authorize(config)
const userData = await requestUserInfo(result.accessToken)
await AsyncStorage.setItem(USER_INFO, JSON.stringify(userData.data))
await AsyncStorage.setItem(TOKEN_KEY, result.accessToken)
await AsyncStorage.setItem(
TOKEN_EXPIRATION_DATE,
result.accessTokenExpirationDate
)
return true
} catch (error) {
return false
}
}
export const logout = async () => {
await AsyncStorage.removeItem(USER_INFO)
await AsyncStorage.removeItem(TOKEN_KEY)
await AsyncStorage.removeItem(TOKEN_EXPIRATION_DATE)
}
I tried to revoke the token but it didn't work.
I expected the user to be able to switch accounts after logging out
Related
How to get access_token from next_auth to use it with googleapis,
lets say i am creating a crud app that store the data in google drive, I am using nextjs and next-auth for OAuth implementation for google. i found this blog so i implemented it. but it logs undefined.
src/pages/api/auth/[...nextauth].ts
import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import jwt from 'next-auth/jwt'
const secret = process.env.SECRET
export default NextAuth({
// Configure one or more authentication providers
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
authorization:{
params:{
scope:"openid https://www.googleapis.com/auth/drive.file"
}
}
}),
],
secret: process.env.SECRET,
callbacks: {
jwt: ({token, user, account, profile, isNewUser})=> {
console.log({token,user,account,profile})
if (account?.accessToken) {
token.accessToken = account.accessToken;
}
return token;
},
session: async ({session, user,token}) => {
session.user = user;
session.token = token;
return session
}
},
});
and I created a route with nextjs to get the access token
import {getToken,decode} from 'next-auth/jwt'
const handler = async(req, res)=> {
const secret = process.env.SECRET
const token = await getToken({ req, secret });
const accessToken = token.accessToken;
console.log(accessToken)
}
export default handler
any help would be great. thanks
the google's token is stored in account.access_token not account.accessToken. so the jwt callback must be
callbacks: {
jwt: ({token, account })=> {
if (account?.access_token) {
token.access_token = account.access_token;
}
return token;
},
},
and it is better not to expose tokens on clients side which I done in session callback. it is insecure.
As stated in the documentation, you must forward any data you want to be available in the token, such is your accessToken value:
The session callback is called whenever a session is checked. By default, only a subset of the token is returned for increased security. If you want to make something available you added to the token through the jwt() callback, you have to explicitly forward it here to make it available to the client.
So, you just have to add this to your session callback:
session.accessToken = token.accessToken;
I'm trying to get a new token from Google OAuth2 but I keep getting this error:
Here is my code (I'm using Expo to build React Native apps):
const uri = 'https://oauth2.googleapis.com/token'
const headerr = {
'Content-Type': 'Content-Type: application/x-www-form-urlencoded'
}
const bodyy = {
"client_id": '******************',
"refresh_token": `${refreshToken}`,
"grant_type":"refresh_token",
"access_type":"offline"
}
const fitnesss = await fetch(uri, {
method: "POST",
headers: headerr,
body: JSON.stringify(bodyy)
});
fitnesss.json().then(res => {
console.log(res)
})
Does anyone know how to solve this?
unsupported grant type means that the grant type refresh token is not supported with the language you are using.
The reason for this is that JavaScript is client sided which would mean that you would need to have the refresh token in the code. Anyone who viewed the source in the browser would be able to see and use your refresh token.
To use refresh tokens use a server sided language. For example Node.js
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/drive.metadata.readonly'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = 'token.json';
// Load client secrets from a local file.
fs.readFile('credentials.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
// Authorize a client with credentials, then call the Google Drive API.
authorize(JSON.parse(content), listFiles);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
function authorize(credentials, callback) {
const {client_secret, client_id, redirect_uris} = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(
client_id, client_secret, redirect_uris[0]);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, (err, token) => {
if (err) return getAccessToken(oAuth2Client, callback);
oAuth2Client.setCredentials(JSON.parse(token));
callback(oAuth2Client);
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
* #param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback for the authorized client.
*/
function getAccessToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});
console.log('Authorize this app by visiting this url:', authUrl);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question('Enter the code from that page here: ', (code) => {
rl.close();
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error('Error retrieving access token', err);
oAuth2Client.setCredentials(token);
// Store the token to disk for later program executions
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) return console.error(err);
console.log('Token stored to', TOKEN_PATH);
});
callback(oAuth2Client);
});
});
}
I am able to generate the authorization url, and the code is sucessfully returned to my express endpoint. All this is done in separate methods that I won't bother showing here - simple express routes. They have no affect on the code. I have copy/pasted the auth code from my browser cookies and made a test script that simply calls these functions:
const snoowrap = require('snoowrap');
module.exports = {
getAuthURL: async (managerId, modelId) => {
return snoowrap.getAuthUrl({
clientId: process.env.RDT_CLIENT,
scope: ['privatemessages', 'identity', 'read', 'submit'],
redirectUri: process.env.REDIRECT_URI,
permanent: false,
state: `${managerId}-${modelId}`
});
},
requester: async (token) => {
let s;
try {
s = await snoowrap.fromAuthCode({
code: token,
userAgent: process.env.RDT_AGENT,
clientId: process.env.RDT_CLIENT,
redirectUri: process.env.REDIRECT_URI,
clientSecret: process.env.RDT_SECRET,
});
} catch (err) {
if (err) {
console.log(err);
}
}
return s;
}
}
require('dotenv').config();
(async () => {
const requester = await require('./reddit/snoowrap').requester('<CODE GOES HERE>');
const me = await requester.getMe();
console.log(me);
})();
Calling this function results in this error:
RequestError: API Error: invalid_grant - undefined
I have a feeling I am just making a simple mistake here. If anyone with more experience than me could show me the path I would be eternally grateful!
Edit:
Made some progress - I am able to use the grant code a single time, even though I set it to permanent. I think what is happening is when I stop the application, the instance of snoowrap that created the grant code is now destroyed, leaving a useless session cookie behind with it. Can anyone confirm this?
Edit 2:
I made a new access token from authUrl, made a call to snoowrap.me() with that token once, used updateAccessToken() method to update the token and set it to browser cookies, and tried to use the updated token for the next request and it still said invalid_grant on the second function call. Why??
I have referred this link https://medium.com/coinmonks/link-your-amazon-alexa-skill-with-a-google-api-within-5-minutes-7e488dc43168 and used same configuration as stated.
I am able to get access token in the lambda function var accesstoken =handlerInput.requestEnvelope.context.System.user.accessToken;
How to get refresh token in the handlerinput event by configuring the alexa developer console account linking section?
I have tried enable/disable skill in companion app,Tested with simulator,Removing alexa skill from the google auto access and then allowing access.
LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'LaunchRequest' || (handlerInput.requestEnvelope.request.type === 'IntentRequest' && handlerInput.requestEnvelope.request.intent.name === 'LaunchRequest');
},
async handle(handlerInput) {
console.log('LAUNCH REQUEST CALLED');
const speechText = 'Welcome!';
if (handlerInput.requestEnvelope.context.System.user.accessToken === undefined) {
console.log('ACCESS TOKEN NOT FOUND IN LAUNCH REQUEST');
return handlerInput.responseBuilder
.speak("ACCESS TOKEN NOT FOUND IN LAUNCH REQUEST")
.reprompt("ACCESS TOKEN NOT FOUND IN LAUNCH REQUEST")
.withLinkAccountCard()
.withShouldEndSession(true)
.getResponse();
}
const fs = require('fs');
const readline = require('readline');
const { google } = require('googleapis');
const SCOPES = ['https://www.googleapis.com/auth/userinfo.email','https://www.googleapis.com/auth/userinfo.profile','https://www.googleapis.com/auth/plus.me','https://www.googleapis.com/auth/tasks.readonly','https://www.googleapis.com/auth/tasks'];
function authorize() {
return new Promise((resolve) => {
const client_secret = process.env.client_secret;
const client_id = process.env.client_id;
const redirect_uris = ['*******************************', '*******************************', '*******************************'];
const oAuth2Client = new google.auth.OAuth2(
client_id, client_secret, redirect_uris[0]);
console.log('access token found : ' + handlerInput.requestEnvelope.context.System.user.accessToken);
oAuth2Client.credentials = { "access_token": handlerInput.requestEnvelope.context.System.user.accessToken };
The refresh token is not exposed to the Skill by Alexa, in other words : there is no way for your skill code to get access to the refresh token, this is entirely managed by Alexa. Alexa will use the refresh token behind the scene to ask your Identity Provider (Google in your case) a fresh token when your customer will access your skill and the access token is about to expire.
This is explained in Alexa Account Linking documentation at https://developer.amazon.com/docs/account-linking/account-linking-for-custom-skills.html#choose-auth-type-overview
OAuth2 is producing "Username and Password not accepted" error when try to send email with Gmail+ Nodejs+Nodemailer
Code - Nodejs - Nodemailer and xoauth2
var nodemailer = require("nodemailer");
var generator = require('xoauth2').createXOAuth2Generator({
user: "", // Your gmail address.
clientId: "",
clientSecret: "",
refreshToken: "",
});
// listen for token updates
// you probably want to store these to a db
generator.on('token', function(token){
console.log('New token for %s: %s', token.user, token.accessToken);
});
// login
var smtpTransport = nodemailer.createTransport({
service: 'gmail',
auth: {
xoauth2: generator
}
});
var mailOptions = {
to: "",
subject: 'Hello ', // Subject line
text: 'Hello world ', // plaintext body
html: '<b>Hello world </b>' // html body
};
smtpTransport.sendMail(mailOptions, function(error, info) {
if (error) {
console.log(error);
} else {
console.log('Message sent: ' + info.response);
}
smtpTransport.close();
});
issues:
I used Google OAuth2 playground to create the tokens, https://developers.google.com/oauthplayground/
It looks to grab a valid accessToken ok, using the refreshToken, (i.e. it prints the new access token on the screen.) No errors until it tries to send the email.
I added the optional accessToken: but got the same error. ( "Username and Password not accepted")
I am not 100% sure about the "username", the docs say it needs a "user" email address - I guess the email of the account that created to token, but is not 100% clear. I have tried several things and none worked.
I have searched the options on the gmail accounts, did not find anything that looks wrong.
Also, when I did this with Java, it needed the google userID rather than the email address, not sure why this is using the email address and the Java is using the UserId.
nodemailer fails with a "compose" scope
The problem was the "scope"
it fails with:
https://www.googleapis.com/auth/gmail.compose
but works ok if I use
https://mail.google.com/
Simply just do the following:
1- Get credentials.json file from here https://developers.google.com/gmail/api/quickstart/nodejs press enable the Gmail API and then choose Desktop app
2- Save this file somewhere along with your credentials file
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
// If modifying these scopes, delete token.json.
const SCOPES = ['https://mail.google.com'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = 'token.json';
// Load client secrets from a local file.
fs.readFile('credentials.json', (err, content) => {
if(err){
return console.log('Error loading client secret file:', err);
}
// Authorize the client with credentials, then call the Gmail API.
authorize(JSON.parse(content), getAuth);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
function authorize(credentials, callback) {
const {client_secret, client_id, redirect_uris} = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, (err, token) => {
if(err){
return getNewToken(oAuth2Client, callback);
}
oAuth2Client.setCredentials(JSON.parse(token));
callback(oAuth2Client);
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
* #param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback for the authorized client.
*/
function getNewToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});
console.log('Authorize this app by visiting this url:', authUrl);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question('Enter the code from that page here: ', (code) => {
rl.close();
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error('Error retrieving access token', err);
oAuth2Client.setCredentials(token);
// Store the token to disk for later program executions
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) return console.error(err);
console.log('Token stored to', TOKEN_PATH);
});
callback(oAuth2Client);
});
});
}
function getAuth(auth){
}
3 - Run this file by typing in your terminal: node THIS_FILE.js
4- You'll have token.json file
5- take user information from credentials.json and token.json and fill them in the following function
const nodemailer = require('nodemailer');
const { google } = require("googleapis");
const OAuth2 = google.auth.OAuth2;
const email = 'gmail email'
const clientId = ''
const clientSecret = ''
const refresh = ''
const oauth2Client = new OAuth2(
clientId,
clientSecret,
);
oauth2Client.setCredentials({
refresh_token: refresh
});
const newAccessToken = oauth2Client.getAccessToken()
let transporter = nodemailer.createTransport(
{
service: 'Gmail',
auth: {
type: 'OAuth2',
user: email,
clientId: clientId,
clientSecret: clientSecret,
refreshToken: refresh,
accessToken: newAccessToken
}
},
{
// default message fields
// sender info
from: 'Firstname Lastname <your gmail email>'
}
);
const mailOptions = {
from: email,
to: "",
subject: "Node.js Email with Secure OAuth",
generateTextFromHTML: true,
html: "<b>test</b>"
};
transporter.sendMail(mailOptions, (error, response) => {
error ? console.log(error) : console.log(response);
transporter.close();
});
If your problem is the scopes, here is some help to fix
Tried to add this as an edit to the top answer but it was rejected, don't really know why this is off topic?
See the note here: https://nodemailer.com/smtp/oauth2/#troubleshooting
How to modify the scopes
The scopes are baked into the authorization step when you get your first refresh_token. If you are generating your refresh token via code (for example using the Node.js sample) then the revised scope needs to be set when you request your authUrl.
For the Node.js sample you need to modify SCOPES:
// If modifying these scopes, delete token.json.
-const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly'];
+const SCOPES = ['https://mail.google.com'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
And then the call to oAuth2Client.generateAuthUrl will produce a url that will request authorization from the user to accept full access.
from the Node.js sample:
function getNewToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});