so i am trying to set up twitter oauth, either v1 or v2, to my web app. google oauthv2 is working just fine but when i try passport-twitter-oauth2 it says that there isn't any access toekn, and when i try basic oauthv1.1 auth it sends this error message "APIError: You currently have Essential access which includes access to Twitter API v2 endpoints only. If you need access to this endpoint, you’ll need to apply for Elevated access via the Developer Portal." even though i am not even accessing any endpoint at all i just need it to log my users in. for reference here is my passport.js file:
passport.use(new TwitterStrategy({
consumerKey: TWITTER_KEY,
consumerSecret: TWITTER_SECRET,
callbackURL: "http://localhost:8080/api/user/auth/twitter/callback",
passReqToCallback: true
},
(accessToken, refreshToken, profile, done) => {
console.log(profile);
return done(err, profile)
// User.findOrCreate({ twitterId: profile.id }, function (err, user) {
// return cb(err, user);
// });
}
));
user.get('/auth/twitter',passport.authenticate('twitter'));
user.get('/auth/twitter/callback',passport.authenticate('twitter', { failureRedirect: '/auth/error' }), (req, res) => {
res.redirect('/');
});
Related
I have implemented instagram oauth using passport and passport-instagram package in nodejs.
Below is my code.
require("dotenv").config();
const express = require("express");
const app = express();
const passport = require("passport");
const InstagramStrategy = require("passport-instagram").Strategy;
const ExpressSession = require("express-session");
app.use(passport.initialize());
app.use(
ExpressSession({
resave: false,
saveUninitialized: true,
secret: "mylittlesecret",
})
);
app.use(passport.session());
// Serialize user data to store in the session
passport.serializeUser(function(user, done) {
done(null, user);
});
// Deserialize user data from the session
passport.deserializeUser(function(obj, done) {
done(null, obj);
});
passport.use(
new InstagramStrategy({
clientID: "Instagram_client_id",
clientSecret: "Instagram_client_secret",
callbackURL: "https://d5d0-115-xxx-xx-179.ngrok.io/auth/instagram/callback",
},
function(accessToken, refreshToken, profile, cb) {
process.nextTick(function() {
return cb(null, profile);
});
}
)
);
// Instagram login route
app.get(
"/auth/instagram",
passport.authenticate("instagram", {
scope: [
"pages_show_list",
"user_media",
"user_profile",
"instagram_basic",
"instagram_content_publish",
"instagram_manage_comments",
"instagram_manage_insights",
"pages_read_engagement",
],
})
);
// Instagram callback route
app.get(
"/auth/instagram/callback",
passport.authenticate("instagram", {
failureRedirect: "/error",
}),
(req, res) => {
res.redirect("/profile");
}
);
app.get("/profile", (req, res) => {
res.send("Proile");
});
app.get("/error", (req, res) => {
res.send("error");
});
const PORT = process.env.PORT;
app.get("/", (req, res) => {
res.send("Home");
});
app.listen(PORT, () => {
console.log(`Server is running at ${PORT}`);
});
I have created a consumer facebook app in developers platform and added product of Instagram basic display api , in basic display api valid oauth redirect uri , i have entered : https://d5d0-115-xxx-xx-179.ngrok.io/auth/instagram/callback url which i have declared in my code and
and in deauthroize callback url : https://dd9b-115-xxx-xx-179.ngrok.io/auth/instagram/
I have also added tester insatgram account also
When i hit /auth/instagram route this is taking me to instagram login, after completing all the steps im recieving error like this,
InternalOAuthError: failed to fetch user profile
at /Users/vivekgupta/Desktop/AdosphereServices/instalogin/node_modules/passport-instagram/lib/strategy.js:80:28
at passBackControl (/Users/vivekgupta/Desktop/AdosphereServices/instalogin/node_modules/oauth/lib/oauth2.js:132:9)
at IncomingMessage.<anonymous> (/Users/vivekgupta/Desktop/AdosphereServices/instalogin/node_modules/oauth/lib/oauth2.js:157:7)
at IncomingMessage.emit (node:events:402:35)
at endReadableNT (node:internal/streams/readable:1343:12)
at processTicksAndRejections (node:internal/process/task_queues:83:21)
Please help me with this ,
Thank You In advance.
I am using passport local and google strategies along with express session and storing the session in a mongo database. The front end is Angular, i'm not sure thats relevant but it means my express server is running on (localhost) port 3000 and my angular app is on (localhost) port 4200
Everything works perfectly in the browser. I can login with the local or google strategy, the session is created in the database, the cookie is stored correctly, I can refresh the page and i'm still logged in which is great.
I'm also using Electron to convert my app to a desktop app. The local strategy still works fine (and the session persists correctly) but the google strategy doesn't work for Electron. I can login correctly through google, my passport serializeUser function is called, the session is created in the database and the cookie is stored. However it doesn't look like the deserializeUser function is ever called and when I refresh Electron req.user is empty so the user is not authenticated and I have to login again.
Using google sign in with Electron was a bit of a pain but the flow is:
user presses login button (in angular app)
this sends an IPC message to Electron
my electron app opens a new browser window pointing to the auth/google route and listens for the redirect
the auth/google/redirect route is hit and req.user shows the correct user data
I call req.login and pass the user data
the passport serializeUser function is called and runs successfully
but the deserializeUser function is not called on subsequent requests (whereas it is in the browser) and when I refresh Electron req.user is undefined so I am not authenticated and have to log in again...
I'll post the relevant sections of code below:
Electron Auth Window
export function createAuthWindow2() {
destroyAuthWin();
win = new BrowserWindow({
width: 900,
height: 700,
webPreferences: {
nodeIntegration: false,
enableRemoteModule: false
}
});
console.log('auth-process2, load page: ')
console.log('http://localhost:3000/api/auth/google/desktop')
win.loadURL('http://localhost:3000/api/auth/google/desktop', {userAgent: 'Chrome'})
const {session: {webRequest}} = win.webContents;
ipcMain.on('user', (event, user) => {
console.log('auth process2 user: ', user)
})
const filter = {
urls: [
'http://localhost:3000/api/auth/google/desktop/redirect*'
]
};
webRequest.onBeforeRequest(filter, async ({url}) => {
console.log('auth-process2 onBeforeRequest')
getUser(url)
});
win.on('authenticated', () => {
destroyAuthWin();
});
win.on('closed', () => {
win = null;
});
}
google-desktop route
router.get("/google/desktop", passport.authenticate("google-desktop", {
scope: ["profile", "email"]
})
)
Redirect route
router.get("/google/desktop/redirect", passport.authenticate("google-desktop", { session: false, failureMessage: true }), (req,res) => {
// console.log('auth.ts route, google/desktop/redirect failure messages: ', req.session.messages)
console.log('auth.ts route, google/desktop/redirect req.user: ', req.user)
req.login(req.user, (err) => {
console.log('auth.ts route, google/desktop/redirect req.login err: ', err)
console.log('auth.ts route, google/desktop/redirect req.login cookies: ', req.cookies)
if (err) {
res.status(401).json({ message: err })
}
else {
res.status(200).json({ user: req.user })
}
})
})
Passport serialize and deserialize user functions
// serialize the user to store the user ID in the session
passport.serializeUser((user, done) => {
console.log('passport serializeUser, user: ', user)
return done(null, user._id)
})
// gets user id from session, then get user from database
passport.deserializeUser((id, done) => {
console.log('passport deserializeUser, id: ', id)
User.findById(id, (err, user) => {
if (err) {
console.log('passport deserializeUser error: ', err)
return done(err)
}
console.log('passport deserializeUser success user: ', user)
return done(null, user)
})
})
Express session and passport initialization
app.use(cookieParser())
// Parsers for POST data
app.use(bodyParser.json()) // for parsing application/json
app.use(bodyParser.urlencoded({ extended: false }))
// set up CORS since server (port 3000) and angular application (port 4200) are running on different ports
// app.use(cors())
app.use(cors({origin: [
"http://localhost:4200",
"http://localhost:3000"
], credentials: true}))
// express session middleware
const expressSession = session({
secret: 'random secret', // secret should be randomly generated string pulled from environment variable
resave: false, // option to save/update session whenever user refreshes page, even if no data has changed
// saveUninitialized: true, // create session if user visits site (when set to true), or only when user logs in (when set to false)
store: new MongoStore({ mongooseConnection: mongoose.connection }),
cookie: {
maxAge: 2.628e+9, // one month
secure: false,
httpOnly: false, // to access cookie in javascript/angular we need to set httpOnly to false. But this will not get the socket.io id
sameSite: 'none'
}
})
// install express session middleware
app.use(expressSession)
// Passport
console.log('server/index initializing passport and session')
app.use(passport.initialize())
app.use(passport.session())
I am implementing MSAL authentication for our SPA. I've followed various official and unofficial guides and so far this is my auth implementation:
loginMicrosoft() {
myMSALObj
.loginPopup(msalConfig.loginRequest)
.then((response) => {
console.log(response);
this.username = response.account.userName;
this.account = response.account;
})
.catch((error) => {
console.error(error);
});
},
readEvents() {
this.getTokenPopup(msalConfig.tokenRequest)
.then((response) => {
console.log("silent token!: ", response);
this.callMSGraph(
graphConfig.graphConfig.graphGetCalendarEventsEndpoint,
response.accessToken
);
})
.catch((error) => {
console.error(error);
});
},
createEvent() {
this.getTokenPopup(msalConfig.tokenRequest)
.then((response) => {
this.callMSGraphCreateEvent(
graphConfig.graphConfig.graphCreateCalendarEventEndpoint,
response.accessToken
);
})
.catch((error) => {
console.error(error);
});
},
getTokenPopup(request) {
request.account = this.account;
console.log(request);
return myMSALObj.acquireTokenSilent(request).catch((error) => {
console.warn(
"silent token acquisition fails. acquiring token using popup : ",
error
);
if (error instanceof Msal.InteractionRequiredAuthError) {
// fallback to interaction when silent call fails
return myMSALObj
.acquireTokenPopup(request)
.then((tokenResponse) => {
console.log(tokenResponse);
return tokenResponse;
})
.catch((error) => {
console.error(error);
});
} else {
console.warn(error);
}
});
},
async callMSGraph(endpoint, token) {
console.log("request made to Graph API at: " + new Date().toString());
const resp = await axios.post("/api/microsoft/get-events", { endpoint, token });
this.calendarEvents = resp.data.value;
console.log("vaste: ", resp);
},
async callMSGraphCreateEvent(endpoint, token) {
console.log("request made to Graph API at: " + new Date().toString());
const resp = await axios.post("/api/microsoft/create-event", {
endpoint,
token,
});
this.calendarEvents = resp.data.value;
console.log("vaste: ", resp);
},
Everything works as intended, until the access token is reaching expiry.
If AcquireTokenSilent is called 5 minutes before the expiration of after the expiration of the access token, I would expect it to return a new access token, using the hidden refresh token in the MSAL cache. Instead, I get the following error:
silent token acquisition fails. acquiring token using popup :
InteractionRequiredAuthError: Silent authentication was denied. The
user must first sign in and if needed grant the client application
access to the scope 'User.Read Calendars.ReadWrite openid
profile'.
It doesn't seem to be normal to ask for user sign-in every hour, and I cant seem to find any resources on this issue..
Any help is greatly appreciated!
EDIT:
I tried adding offline_access to my token request scopes. My scopes setup is following:
export const loginRequest = {
scopes: ["User.Read", "Calendars.ReadWrite", "offline_access"],
};
export const tokenRequest = {
scopes: ["User.Read", "Calendars.ReadWrite", "offline_access"],
forceRefresh: false, // Set this to "true" to skip a cached token and go to the server to get a new token
};
Now im getting the login popup with the following error every time i try to call the api:
InteractionRequiredAuthError: Silent authentication was denied. The user must first sign in and if needed grant the client application access to the scope 'User.Read Calendars.ReadWrite offline_access openid profile'.
Probably your scopes (in your app registration or in your msal config, depending on where you define your config and if you are using .default scope) do not include the request for the offline_access scope. Please include it, it is required if you want to (auto-) refresh your tokens. If you don't get a new consent prompt after adding this scope (user must agree, i.e. give consent to this), just reset your app consents in azure portal, or consent manually.
I am using ADAL js to authenticate user and i am able to authenticate user successfully. I am able to fetch a token for graph api and read user profile with following url.
GET
https://graph.microsoft.com/v1.0/me
But i am not able to read user profile picture:
https://graph.microsoft.com/v1.0/me/photo/$value
I get the following error
Object { code: "NoPermissionsInAccessToken", message: "The token contains no permissions, or permissions can not be understood.", innerError: {…} }
I have set the required permissions:
Is there any way i can check why i am able to fetch profile but not photo.
Content of JWT sent in header before it received 401 error:
{
"typ": "JWT",
"nonce": "IenxIPCU1ue14Z9bIIxEidRBBCTnL52w4Q",
"alg": "RS256",
"x5t": "huN95IvPf34GzBDZ1GXGirnM",
"kid": "huN95hq34GzBGXGirnM"
}
Body of JWT token:
{
"aud": "https://graph.microsoft.com",
"iss": "https://sts.windows.net/6f1dc6d4-8e90-4593/",
"iat": 1596560469,
"nbf": 1596560469,
"exp": 1596564369,
"acct": 1,
"acr": "1",
"aio": "ATQAy/8QAAAAf64iQ9pAkP+bk/JnXpSNXFPVFqvW/urra8A2QueWm2xaJZM+",
"altsecid": "5::100320A47F8DD5",
"amr": [
"wia"
],
"app_displayname": "graphdemo-dev",
"appid": "dsfkj32-4350-44a4-dd33-f45b7172b0cd",
"appidacr": "0",
"email": "email#domain.com",
"family_name": "faily",
"given_name": "given",
"idp": "https://sts.windows.net/deff24bb-2089-4400378b2/",
"in_corp": "true",
"ipaddr": "70.50.13.18",
"oid": "dskfs77s-5bc6-4fbe-b59a-11fbc2",
"platf": "3",
"puid": "A9BDE43D",
"scp": "profile User.Read User.Read.All User.ReadBasic.All User.ReadWrite User.ReadWrite.All",
"sub": "r4-9Ra9nHTjU-g1PvuXwh18",
"tenant_region_scope": "NA",
"tid": "d4-8e90-4599-af70-13a4289b3",
"unique_name": "email#domain.com",
"uti": "MDGPXbP3lUJMyAA",
"ver": "1.0",
"xms_tcdt": 8700342
}
Note: I removed and updated confidential data with random chars.
When i tried on Graph Explorer:
Need admin approval
Graph explorer (official site)
microsoft.com
Graph explorer (official site) needs permission to access resources in your organization that only an admin can grant. Please ask an admin to grant permission to this app before you can use it.
import AuthenticationContext from 'adal-angular/lib/adal.js';
// KPMG
const config = {
tenant: process.env.VUE_APP_AZUREAD_TENANTID,
clientId: process.env.VUE_APP_AZUREAD_CLIENTID,
cacheLocation: process.env.VUE_APP_CACHE_LOCATION,
redirectUri: process.env.VUE_APP_REDIRECT_URI
};
export default {
authenticationContext: null,
/**
* #return {Promise}
*/
initialize() {
this.authenticationContext = new AuthenticationContext(config);
return new Promise((resolve, reject) => {
if (this.authenticationContext.isCallback(window.location.hash) || window.self !== window.top) {
// redirect to the location specified in the url params.
this.authenticationContext.handleWindowCallback();
}
else {
// try pull the user out of local storage
const user = this.authenticationContext.getCachedUser();
if (user) {
this.authenticationContext.config.extraQueryParameter = 'login_hint=' + user.userName;
resolve();
}
else {
// no user at all - go sign in..
this.signIn();
}
}
});
},
I use below code to get graph api token
acquireGraphApiToken() {
return new Promise((resolve, reject) => {
this.authenticationContext.acquireToken('https://graph.microsoft.com', (error, graphApiToken) => {
if (error || !graphApiToken) {
this.signOut();
return reject(error);
} else {
return resolve(graphApiToken);
}
});
});
},
For Microsoft Graph explorer, you need to sign in with an admin account and do the admin consent like this:
Do the admin consent:
And from the screenshot above, you can see the access token. After you finish the admin consent, you can decode the access token to see if it includes the required permissions.
For you own Azure AD application, I see that you have done the admin consent based on your screenshot. It's hard to say where the problem is. So my suggestion is to try the admin consent endpoint:
// Line breaks are for legibility only.
GET https://login.microsoftonline.com/{tenant}/v2.0/adminconsent?
client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&state=12345
&redirect_uri=http://localhost/myapp/permissions
&scope=
https://graph.microsoft.com/calendars.read
https://graph.microsoft.com/mail.send
Access this url in a browser using an admin account and finish the consent. If the issue still exists, you can create a new Azure AD application and only add the required permission User.Read (Don't add other permissions).
I am trying to setup a simple example of 3rd party authentication in a localhost parse server using GitHub. I read the parse guide, the wiki, as well as old issues and websites (pre- and post- the opensourcing of parse). Almost everything is working, but the last part: the link between the GitHub access token and the Parse.User.
Here's my client and server code.
Client code (using hello.js for connecting with github and getting the access_token):
<html>
<body>
<script src="src/hello.polyfill.js"></script>
<script src="src/hello.js"></script>
<script src="src/modules/github.js"></script>
<script src="https://npmcdn.com/parse/dist/parse.min.js"></script>
<button onclick="hello('github').login()">Login with GitHub</button>
<div id='profile'></div>
<script>
const parseClientID = "[MY_PARSE_APP_ID]";
const githubClientID = "[MY_GITHUB_APP_ID]";
Parse.initialize(parseClientID);
Parse.setURL = "http://localhost:1337/parse";
var provider = {
authenticate(options) {if (options.success) {options.success(this, {});}},
restoreAuthentication(authData) {},
getAuthType() {return 'github';},
deauthenticate() {}
};
let authData = {authData: {access_token: 'REPLACED_ON_THE_FLY', id: githubClientID}};
hello.init({github: githubClientID}, {
oauth_proxy: 'http://localhost:3000/proxy',
redirect_uri: 'http://localhost:3000/redirect'
});
// after loging in, when github calls back, this part of the code tries to
// link the github data with a Parse.User
hello.on('auth.login', (auth) => {
authData.authData.access_token = auth.authResponse.access_token;
var user = new Parse.User();
user._linkWith(provider, authData).then(usr=>console.log(usr), err=>console.log(err));
});
</script>
</body>
</html>
Server code (nothing fancy, standard parse-server route, and oauthshim to talk with hello.js):
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var ParseServer = require('parse-server').ParseServer;
var oauthshim = require('oauth-shim');
var app = express();
app.get('/', (req, res) => {res.render('index');});
app.get('/redirect', (req, res) => {res.render('redirect');});
var api = new ParseServer({
"appId": "[MY_PARSE_APP_ID]",
"masterKey": "[MY_PARSE_MASTER_KEY]",
"appName": "connect",
"databaseURI": "mongodb://127.0.0.1:27017/parse",
"serverURL": "http://localhost:1337/parse",
"auth": {"github": {"id":"[MY_GITHUB_APP_ID]","access_token":"spaceholder"}}
});
app.use('/parse', api);
oauthshim.init([{
client_id: '[MY_GITHUB_APP_ID]',
client_secret: '[MY_GITHUB_SECRET]',
grant_url: 'https://github.com/login/oauth/access_token',
domain: 'http://localhost:3000/redirect'
}]);
app.use('/proxy', oauthshim);
app.listen(1337, function() {console.log('parse-server running on port 1337.');});
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(function(req, res, next) { next(createError(404));});
app.use(function(err, req, res, next) {
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
The client displays a single "login" button. On clicking, it connects to github, gets an access token, which is then used to user._linkWith().
At this point, I get this error in the Web console:
error: Github auth is invalid for this user. code=101, message=Github auth is invalid for this user.
I think that I'm not writing the auth object properly, but I can't figure out how to do it just from the Custom Authentication section of the Guide in the parse-server website (https://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication).
Thank you in advance !
I have implemented the github login but not in the same way you did but I think it should work the process should be the same.
You need to get the access token and next with the token get the github user id at https://api.github.com/user. And finaly call the _linkWith method.
On the server side you don't need to add auth configuration. your server should be :
var api = new ParseServer({
"appId": "[MY_PARSE_APP_ID]",
"masterKey": "[MY_PARSE_MASTER_KEY]",
"appName": "connect",
"databaseURI": "mongodb://127.0.0.1:27017/parse",
"serverURL": "http://localhost:1337/parse",
});
And on client client side you don't need to configure the provider. Just call _linkWith like that :
hello.on('auth.login', (auth) => {
// get the github user id before
const authData = {
id: 'your github user id'
access_token: 'your access token'
}
const user = new Parse.User()
return user._linkWith('github', { authData }).then(user => {
// do what you want with user
})
Hope this will help you.