Forge 3-Legged Oauth - Sign in as another User / Force logout - autodesk-forge

I got 3 legged to work, and I have my own session login/logout management.
After logout, if user wants to login again, and I send him to
https://developer.api.autodesk.com/authentication/v1/authorize, it's going straight to my callback with the previous user already authenticated instead of prompting for new login.
Seems like Autodesk is storing the session as a cookie so the only way to switch users after authorizing is to clear out the browser cache/data
Is there a way to force logout, or something similar to "Sign in as another user"?
This is my first time doing oauth, so I am not sure I am missing something, but seems like there should be way to force clear out the session and force a new login.
Edit
Let me further clarify what I am trying to achieve:
Here is what I have.
1. I direct the user to the authentication page directing it to:
https://developer.api.autodesk.com/authentication/v1/authorize?response_type=code&client_id=obQDn8P0GanGFQha4ngKKVWcxwyvFAGE&redirect_uri=http%3A%2F%2F{{mycallback}}%2Fcallback%3Ffoo%3Dbar&scope=data:read
The user logins in with his Autodesk credentials
The authorization flow redirect's the user to `mycallback.com/callback/?code={{code}}
My backend get's the code parameter from the url request, and makes a POST request to https://developer.api.autodesk.com/authentication/v1/gettoken
The request returns, among other things, an access_token
I store the access_token to the user's session, and use that to make the next requests to the API.
Up to this point, it works as I expect. Now I want to log the user out, and potentially sign in as a different user.
A /logout end point on my server clears the user session, eliminating the stored access_token.
Once the back end realized there is not active session/access_token, it redirect the user to the authentication flow (step #1 above).
At this point, I would expect to see another Autodesk login page, but instead, Autodesk's server automatically authorizes without a new login, and redirects user to call back, and the user logs in again.
So to rephrase my question, how do change the behavior on #9 above, so that the user has to re-enter his credential?
I am running into this often during development, where I login with my personal account, then I logout, and I would like to login with my work account.
Currently, the only way i can do that, is by Clearing my browser's cache.
That leads me to think Autodesk is storing the session in the browser, and that's why it's re-authenticating without getting new credentials.
The same behaviour happens on your dm.autodesk.io
After the first login, if i try to authorize again, I am not prompted for an login the 2nd time, instead it automatically re-logins in the first user I logged in with.
If I understand what's happening correctly, seems that the API should have an end-point that we can call when I user logs out to force a re-authentication.
Makes sense?
Thanks!

Just load this URL: "https://accounts.autodesk.com/Authentication/LogOut"
This will log out previous user session and a new user can logged in.
Refer this for more details: https://forge.autodesk.com/blog/log-out-forge

I'm not sure what you mean by "I send him to https://developer.api.autodesk.com/authentication/v1/authorize" but this has to happen on your server.
Check my sample at https://dm.autodesk.io: Allow popups and click the "User Data" in the navbar. Once you logged in, the user session is stored securely on the server. If you reload the page it will log you in automatically. If you click the navbar button again it will clear the session on the server and if you reload you will not be logged in. I guess that's the behaviour you are looking for.
The code for this project is located there. The 3-legged logic is handled from there and look as below (node.js):
import ServiceManager from '../services/SvcManager'
import { serverConfig as config } from 'c0nfig'
import { OAuth2 } from 'oauth'
import express from 'express'
module.exports = function() {
var router = express.Router()
///////////////////////////////////////////////////////////////////////////
// 2-legged client token: exposes a 'data:read' only token to client App
//
///////////////////////////////////////////////////////////////////////////
router.get('/token/2legged', async(req, res) => {
try {
var forgeSvc = ServiceManager.getService('ForgeSvc')
var token = await forgeSvc.request2LeggedToken('data:read')
res.json(token)
} catch (error) {
res.status(error.statusCode || 404)
res.json(error)
}
})
/////////////////////////////////////////////////////////////////////////////
// Initialize OAuth library
//
/////////////////////////////////////////////////////////////////////////////
var oauth2 = new OAuth2(
config.forge.oauth.clientId,
config.forge.oauth.clientSecret,
config.forge.oauth.baseUri,
config.forge.oauth.authorizationUri,
config.forge.oauth.accessTokenUri,
null)
/////////////////////////////////////////////////////////////////////////////
// login endpoint
//
/////////////////////////////////////////////////////////////////////////////
router.post('/login', function (req, res) {
var authURL = oauth2.getAuthorizeUrl({
redirect_uri: config.forge.oauth.redirectUri,
scope: config.forge.oauth.scope.join(' ')
})
res.json(authURL + '&response_type=code')
})
/////////////////////////////////////////////////////////////////////////////
// logout endpoint
//
/////////////////////////////////////////////////////////////////////////////
router.post('/logout', (req, res) => {
var forgeSvc = ServiceManager.getService(
'ForgeSvc')
forgeSvc.delete3LeggedToken(req.session)
res.json('success')
})
/////////////////////////////////////////////////////////////////////////////
// Reply looks as follow:
//
// access_token: "fk7dd21P4FAhJWl6MptumGkXIuei",
// refresh_token: "TSJpg3xSXxUEAtevo3lIPEmjQUxXbcqNT9AZHRKYM3",
// results: {
// token_type: "Bearer",
// expires_in: 86399,
// access_token: "fk7dd21P4FAhJWl6MptumGkXIuei"
// }
//
/////////////////////////////////////////////////////////////////////////////
router.get('/callback/oauth', (req, res) => {
var socketSvc = ServiceManager.getService(
'SocketSvc')
// filter out errors (access_denied, ...)
if (req.query && req.query.error) {
if (req.session.socketId) {
socketSvc.broadcast(
'callback', req.query.error,
req.session.socketId)
}
res.json(req.query.error)
return
}
if(!req.query || !req.query.code) {
res.status(401)
res.json('invalid request')
return
}
oauth2.getOAuthAccessToken(
req.query.code, {
grant_type: 'authorization_code',
redirect_uri: config.forge.oauth.redirectUri
},
function (err, access_token, refresh_token, results) {
try {
var forgeSvc = ServiceManager.getService(
'ForgeSvc')
var token = {
scope: config.forge.oauth.scope,
expires_in: results.expires_in,
refresh_token: refresh_token,
access_token: access_token
}
forgeSvc.set3LeggedTokenMaster(
req.session, token)
if(req.session.socketId) {
socketSvc.broadcast(
'callback',
'success',
req.session.socketId)
}
res.end('success')
} catch (ex) {
res.status(500)
res.end(ex)
}
}
)
})
/////////////////////////////////////////////////////////////////////////////
// logout route
//
/////////////////////////////////////////////////////////////////////////////
router.post('/logout', (req, res) => {
var forgeSvc = ServiceManager.getService(
'ForgeSvc')
forgeSvc.logout(req.session)
res.json('success')
})
///////////////////////////////////////////////////////////////////////////
// 3-legged client token: exposes a 'data:read' only token to client App
//
///////////////////////////////////////////////////////////////////////////
router.get('/token/3legged', async (req, res) => {
var forgeSvc = ServiceManager.getService(
'ForgeSvc')
try {
var token = await forgeSvc.get3LeggedTokenClient(
req.session)
res.json({
expires_in: forgeSvc.getExpiry(token),
access_token: token.access_token,
scope: token.scope
})
} catch (error) {
forgeSvc.logout(req.session)
res.status(error.statusCode || 404)
res.json(error)
}
})
return router
}

Related

how to check if user id or conversation id or user alreadt exists in sunshine via node.js

I am auto replying a predefined welcome message to user when they are posting a message via fb page or WhatsApp. I want if user already exists in sunshine then not to reply to that user else reply them with welcome message. How can I check if user already exists?
'use strict';
// Imports
`enter code here`const express = require('express');
const bodyParser = require('body-parser');
const SunshineConversationsApi = require('sunshine-conversations-client');
var request = require('request')
// Config
let defaultClient = SunshineConversationsApi.ApiClient.instance;
let basicAuth = defaultClient.authentications['basicAuth'];
basicAuth.username = 'app_XXXXXXX';
basicAuth.password = 'XXXXXXXXXXXXXXXXX-bSwG85aM8qldJzTt4rgZlf_XQukWKE8ADMno85g';
const PORT = 5050;
const apiInstance = new SunshineConversationsApi.MessagesApi()
// Server https://expressjs.com/en/guide/routing.html
const app = express();
app.use(bodyParser.json());
// Expose /messages endpoint to capture webhooks
https://docs.smooch.io/rest/#operation/eventWebhooks
app.post('/messages', function(req, res) {
console.log('webhook PAYLOAD:\n',
JSON.stringify(req.body.events[0].payload.conversation.id, null, 4));
const appId = req.body.app.id;
const trigger = req.body.events[0].type;
// Call REST API to send message https://docs.smooch.io/rest/#operation/postMessage
if (trigger === 'conversation:message') {
const authorType = req.body.events[0].payload.message.author.type;
const displayName=req.body.events[0].payload.message.author.displayName;
if(authorType === 'user'){
const conversationId = req.body.events[0].payload.conversation.id;
console.log(conversationId);
console.log(displayName);
check_conversation_ID_with_sunshine(appId,conversationId);
//sendMessage(appId, conversationId);
res.end();
}
}
});
// Listen on port
app.listen(PORT, () => {
console.log(`App listening on port ${PORT}`);
});
async function sendMessage(appId, conversationId){
let messagePost = new SunshineConversationsApi.MessagePost();
messagePost.setAuthor({type: 'business'});
messagePost.setContent({type: 'text', text: 'Thanks boy'});
let response = await apiInstance.postMessage(appId, conversationId, messagePost);
//console.log('API RESPONSE:\n', response);
}
So [a simple] overall process could be:
User message comes in (including userId and conversationId)
fetch user's conversation
if len(response['messages']) == 1 (messages in the conversation)
send welcome message
(else no-op)
The caveat is that you could get false-negatives, if users send multiple messages, in quick succession, right at the beginning (instead of just one).
Some safe ways to do it would be to either:
look for the welcome message at the start of the convo (second message?)
i.e. iterate over [the first few] response['messages'], inspecting each one's text/metadata
when sending the welcome message, add a metadata flag on the conversation (e.g.: metadata.['welcomeSent']: True), searching for this flag for returning users
Doc references
Get a conversation's messages
doc page: https://docs.smooch.io/rest/#operation/listMessages
endpoint: https://api.smooch.io/v2/apps/{appId}/conversations/{conversationId}/messages
authentication: Basic, etc.
payload: <none>
response payload: {messages:[...], meta:{...}, links:{}}

connection in channels.js returns undefined?

I'm trying to establish a real-time socket connection to my client
side via feathers channels. It works without any sort of
authentication. But if i add the following login action scoket is
throwing a weak map key error.
app.on('login', (authResult, { connection }) => {
console.log(connection) // returns undefined
....
})
This is the error I'm receiving
Unhandled Rejection at: Promise Promise { TypeError:
Invalid value used as weak map key
at WeakMap.set ()
app.on('login', (authResult, { connection }) => {
console.log("============>>", connection)
if (authResult && connection) {
app.channel('anonymous').leave(connection);
if (authResult.user && authResult.user['chanelName']) {
let channelName = authResult.user['chanelName'].toString();
channelName = channelName.substr(0, 5)
app.channel(`channel/${channelName}`).join(connection);
} else
app.channel('authenticated').join(connection)
}
});
The connection object is undefined, i think that causes the problem.
Anu suggestions?
Please provide the client side script.
According to fethers documentation connection can be undefined if there is no real-time connection, e.g. when logging in via REST.
You should authenticate your client.
Sample script
const feathers = require('#feathersjs/feathers');
const socketio = require('#feathersjs/socketio-client');
const io = require('socket.io-client');
const auth = require('#feathersjs/authentication-client');
const socket = io('http://localhost:3031');
const app = feathers();
// Setup the transport (Rest, Socket, etc.) here
app.configure(socketio(socket));
const options = {
header: 'Authorization', // the default authorization header for REST
prefix: '', // if set will add a prefix to the header value. for example if prefix was 'JWT' then the header would be 'Authorization: JWT eyJ0eXAiOiJKV1QiLCJhbGciOi...'
path: '/authentication', // the server-side authentication service path
jwtStrategy: 'jwt', // the name of the JWT authentication strategy
entity: 'user', // the entity you are authenticating (ie. a users)
service: 'users', // the service to look up the entity
cookie: 'feathers-jwt', // the name of the cookie to parse the JWT from when cookies are enabled server side
storageKey: 'feathers-jwt', // the key to store the accessToken in localstorage or AsyncStorage on React Native
storage: undefined // Passing a WebStorage-compatible object to enable automatic storage on the client.
}
app.configure(auth(options))
app.authenticate({
strategy: 'jwt',
accessToken: '<JWT TOKEN>'
}).then(() => {
console.log("Auth successfull")
const deviceService = app.service('myService');
deviceService.on('created', message => console.log('Created a message', message));
}).catch(e => {
console.error('Authentication error', e);
// Show login page
});
Hope this will help you.

How to authenticate a java web app with KeyRock?

We are trying to create a user authentication in our web app ( that we are developing in Java Spring MVC). For our authentication we want to use the token and user info acquired from the users fiware.lab account on global instance of keyrock.
Since Keyrock is based on OAuth2 protocol, what is the best approach to use keyrock from our web app?
Is there a java library that we could use for this purpose?
Is there a way to integrate spring security or apache oltu?
Every example would be more than welecome.
We only have the implementation of node.js but we need a java version of this:
var express = require('express');
var OAuth2 = require('./oauth2').OAuth2;
var config = require('./config');
// Express configuration
var app = express();
app.use(express.logger());
app.use(express.bodyParser());
app.use(express.cookieParser());
app.use(express.session({
secret: "skjghskdjfhbqigohqdiouk"
}));
app.configure(function () {
"use strict";
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
//app.use(express.logger());
app.use(express.static(__dirname + '/public'));
});
// Config data from config.js file
var client_id = config.client_id;
var client_secret = config.client_secret;
var idmURL = config.idmURL;
var response_type = config.response_type;
var callbackURL = config.callbackURL;
// Creates oauth library object with the config data
var oa = new OAuth2(client_id,
client_secret,
idmURL,
'/oauth2/authorize',
'/oauth2/token',
callbackURL);
// Handles requests to the main page
app.get('/', function(req, res){
// If auth_token is not stored in a session cookie it sends a button to redirect to IDM authentication portal
if(!req.session.access_token) {
res.send("Oauth2 IDM Demo.<br><br><button onclick='window.location.href=\"/auth\"'>Log in with FI-WARE Account</button>");
// If auth_token is stored in a session cookie it sends a button to get user info
} else {
res.send("Successfully authenticated. <br><br> Your oauth access_token: " +req.session.access_token + "<br><br><button onclick='window.location.href=\"/user_info\"'>Get my user info</button>");
}
});
// Handles requests from IDM with the access code
app.get('/login', function(req, res){
// Using the access code goes again to the IDM to obtain the access_token
oa.getOAuthAccessToken(req.query.code, function (e, results){
// Stores the access_token in a session cookie
req.session.access_token = results.access_token;
res.redirect('/');
});
});
// Redirection to IDM authentication portal
app.get('/auth', function(req, res){
var path = oa.getAuthorizeUrl(response_type);
res.redirect(path);
});
// Ask IDM for user info
app.get('/user_info', function(req, res){
var url = config.idmURL + '/user/';
// Using the access token asks the IDM for the user info
oa.get(url, req.session.access_token, function (e, response) {
var user = JSON.parse(response);
res.send("Welcome " + user.displayName + "<br> Your email address is " + user.email + "<br><br><button onclick='window.location.href=\"/logout\"'>Log out</button>");
});
});
// Handles logout requests to remove access_token from the session cookie
app.get('/logout', function(req, res){
req.session.access_token = undefined;
res.redirect('/');
});
console.log('Server listen in port 80. Connect to localhost');
app.listen(80);
Edit 1
Here is my set up:
and the end result error I get when I call the token:
Fiware devguide explains how this oauth2 flow works against KeyRock.
There also, you can find linked several oauth2 implementations like scribe-data, where you can find several examples on how to use oauth2 authentication against some of the most extended social networks.

passport send error by json

I'm making an app with express + passport and angularJS; I want to be able to send any errors produced from passport (such as username taken or no email provided) by json so my angularJS app can receive these errors in a json response. More specifically right now I want to have a json response to my signup POST method that outputs any errors. I have tried to do this for myself and I've search all over the web and stack overflow I just cannot work this out!
Here is my users route file in express:
var express = require('express');
var router = express.Router();
var isAuthenticated = require('../config/isAuthenticated');
module.exports = function(passport){
router.get('/loggedin', function(req, res){
res.send(req.isAuthenticated() ? req.user : '0');
});
router.post('/signup', passport.authenticate('local-signup', {
successRedirect : '/',
failureRedirect : '/signup',
failureFlash: true
}));
router.post('/login', passport.authenticate('local-login'), function(req, res){
res.send(req.user);
});
router.post('/signout', function(req,res){
req.logout();
res.json({redirect: '/'});
});
router.get('/authtest', isAuthenticated, function(req, res){
res.render('authtest', {user: req.user});
});
return router;
};
This is my passport signup strategy:
passport.use('local-signup', new LocalStrategy({
usernameField : 'username',
passwordField : 'password',
passReqToCallback : true
},
function(req, username, password, done){
process.nextTick(function(){
User.findOne({'local.username' : username}, function(err, user){
if(err) return done(err);
if (user) { //username already exists
return done(null, false, {message: 'Username already exists'});
} else if(!req.body.email) { //no email address provided
return done(null, false, {message: 'You must provide an email address!'});
} else {
var newUser = new User();
newUser.local.username = username;
newUser.generateHash(password, function(err, hash){
if(err) return done(err);
newUser.local.password = hash;
});
newUser.email = req.body.email;
newUser.servers = [];
newUser.save(function(err){
if(err) throw err;
return done(null, newUser);
});
};
});
});
}
));
I know looking at my code right now it looks like I haven't tried to solve this myself at all but this is just my latest working code; I have been stuck at this for the past few days!
Any help would be greatly appreciated :)
According to the current code of passport this is probably achievable by passing custom callback to handle all results of authentiction yourself. This callback is given after options or instead of those.
passport( "local-signup", { ... }, callbackFn );
or
passport( "local-login", callbackFn );
This callback is used in all resulting situations of trying to authenticae. It is thus invoked on processing errors like this:
callbackFn( err )
If (all configured) authentications have failed it is called with
callbackFn( null, false, challenge(s), status(es) )
On successfully having authenticated user the callback is invoked like so:
callbackFn( null, user, infos )
with infos optionally provided by strategies.
Now comes the bottom-side: In either situation passport.authenticate() skips usual processing but instantly invokes provided callback to care for the rest. This includes processing of any options passed in call for passport.authenticate() like flashing messages, preparing session and request for containing authenticated user etc.
Since options given passport.authenticate() are never passed into callback there is actually no obvious reason to use both.
When I was stumbling over the very same problem (linking passport-service with angular-js POST request) I declined to consider use of callback a proper solution. This callback isn't documented. And it doesn't even look quite useful for it isn't passing req, res and next to pass any actual request in callback. Thus it makes very little sense to use it at all and I'd expect it to vanish soon or to change its behaviour quite much.
So the second approach was about trying to figure out why there is a problem in AngularJS. Passport is sending plain text Unauthorized in response with status code 401. AngularJS is trying to parse this as JSON and produces Syntax error. The text Unauthorized results from passprt ending response very simply by invoking
res.statusCode = 401;
res.end(http.STATUS_CODES[res.statusCode]);
Thus a proper workaround might try to replace
either text in http.STATUS_CODES though this is having impact on processing further requests and thus isn't preferrable
or res.end() by an overloaded method acting differently if res.statusCode is 401.
Due to affecting any current request, only, I tried the latter. Replaced res.end() might be used to send any text you want:
router.post('/login',
function(req, res, next) {
var _end = res.end;
res.end = function() {
if (res.statusCode === 401) {
return _end('{"status":"Unauthorized"}');
}
return _end.apply(this, arguments);
};
next();
},
passport.authenticate('local-login'),
function(req, res) {
res.send(req.user);
}
);
Alternatively the replaced method might add previously missing response header information on content type, for this was actually causing issues in AngularJS processing that response as JSON by default.
router.post('/login',
function(req, res, next) {
var _end = res.end;
res.end = function() {
if (res.statusCode === 401) {
res.set("Content-Type", "text/plain");
}
return _end.apply(this, arguments);
};
next();
},
passport.authenticate('local-login'),
function(req, res) {
res.send(req.user);
}
);
Finally, either approach is really just a workaround. I think passport is in the need for revising this annoying limitation.

JSON Web Tokens - attach the token to requests

I am trying to set up an authentication via token for my web app. I am using nodejs for the back end and the jwt-simple module to encode/decode the tokens.
I manage to create and handle the tokens from the server to the client. However I struggle to handle the token from the client to the server.
For example, I have a page called profile for which I handle the requests the following way:
app.get('/profile', [bodyParser(), jwtauth], function(req, res) {
res.render('profile.ejs', {
user : req.user // get the user out of session and pass to template
});
});
Where jwtauth is the following:
var jwt = require('jwt-simple');
var User = require('../server/models/user');
 
module.exports = function(req, res, next) {
var token = (req.user && req.user.access_token) || (req.body && req.body.access_token) || (req.query && req.query.access_token) || req.headers['x-access-token'];
if (!token)
return next('invalid or no token');
try {
var decoded = jwt.decode(token, app.get('jwtTokenSecret'));
if (decoded.exp <= Date.now())
return res.end('Access token has expired', 400);
User.findOne({ _id: decoded.id }, function(err, user) {
req.user = user;
});
return next();
} catch (err) {
return next('couldn\'t decode token');
}
};
On the client side I attached the token once the user is logged in the following way:
$(document).ready(function() {
var token = __token;
if (token) {
$.ajaxSetup({
headers: {
'x-access-token': token
}
});
}
});
But if I try to get the url '/profile' in my browser there is no 'x-access-token' in the headers and I get the 'invalid or no token' message.
How can I set the token on the client side so that it is attached to every request to my server?
Many thanks
What are you seeing when you console.log(req.headers) in your jwtauth middleware? Are you deifning $.ajaxSetup more than once? See this post