botAuth and Auth0 and Invalid authorization code
jtsampson opened this issue · 1 comments
Hello,
I'm trying to use botauth
to connect to an Auth0 client using the passport-auth0 strategy to authorize access to my api that sits behind an API gateway.
Using the Bot Framework Emulator, and commenting out lines 36-38 of index.js so I can use a an http
callback address (perhaps that is my issue), I am able to enter my credentials with Auth0 , click login and the bot logs my profile
However.... after authenticating in the browser, the bot redirects to http://WEB_HOSTNAME:PORT/botauth/auth0?state=...
hangs there for a bit, then redirects to http://WEB_HOSTNAME:PORT/botauth/auth0/callback?code=...
with the response:
{"message":"Invalid authorization code"}
and I never get my bot conversation resumed in the emulator.
I wrote up a quick test based on the project's pintrest example it it helps anyone work out what I am doing wrong...any help would be appreciated...
Notes:
- Same behavior whether or not I use
server.use(clientSessions({ secret: BOTAUTH_SECRET }));
- Tested against Auth0 Client of type
Regular Web Application
** Token Endpoint Authentication Method : POST
** Allowed Callback URLs : configured with proper callback URL (http) in this case
** Allowed Logout URLs : hadn't got that far - Tested against Auth0 Client of type
Non Interactive Client
** Token Endpoint Authentication Method : POST
** Allowed Callback URLs : configured with proper callback URL (http) in this case
** Allowed Logout URLs : hadn't got that far
Thanks in advance.
"use strict";
const restify = require("restify");
const builder = require("botbuilder");
const envx = require("envx");
const botauth = require("botauth");
const clientSessions = require("client-sessions");
const passport = require("passport");
const Auth0Strategy = require('passport-auth0')
//contextual service information
const WEBSITE_HOSTNAME = envx("WEB_HOSTNAME");
const PORT = envx("PORT", 3998);
//bot application identity
const MICROSOFT_APP_ID = envx("MICROSOFT_APP_ID");
const MICROSOFT_APP_PASSWORD = envx("MICROSOFT_APP_PASSWORD");
//oauth details for pinterest
const AUTH0_DOMAIN= envx("AUTH0_DOMAIN");
const AUTH0_CLIENT_ID = envx("AUTH0_CLIENT_ID");
const AUTH0_CLIENT_SECRET = envx("AUTH0_CLIENT_SECRET");
//encryption key for saved state
const BOTAUTH_SECRET = envx("BOTAUTH_SECRET");
var server = restify.createServer();
server.use(restify.bodyParser());
server.use(restify.queryParser());
server.use(clientSessions({ secret: BOTAUTH_SECRET }));
// Create chat connector with bot's Microsoft app identity
var connector = new builder.ChatConnector({
appId: MICROSOFT_APP_ID,
appPassword: MICROSOFT_APP_PASSWORD
});
// Create bot builder client and connect it to restify server
var bot = new builder.UniversalBot(connector);
server.post('/api/messages', connector.listen());
// Initialize with the strategies we want to use
var ba = new botauth.BotAuthenticator(server, bot, { baseUrl : "http://" + WEBSITE_HOSTNAME, secret : BOTAUTH_SECRET })
.provider("auth0", (options) => {
return new Auth0Strategy({
domain: AUTH0_DOMAIN,
clientID: AUTH0_CLIENT_ID,
clientSecret: AUTH0_CLIENT_SECRET,
scope: 'openid profile email identities',
//scope: ['openid', 'profile', 'email', 'identities'],
//scope: [ 'read_public', 'read_relationships' ],
callbackURL: options.callbackURL
//callbackURL: options.callbackURL
}, (accessToken, refreshToken, profile, done) => {
profile = profile || {};
profile.accessToken = accessToken;
profile.refreshToken = refreshToken;
profile.provider = "auth0"; // workaround
console.log('===========')
console.log(profile)
console.log('===========')
return done(null, profile);
});
});
/**
* Just a page to make sure the server is running
*/
server.get("/", (req, res) => {
res.send("auth0");
});
//=========================================================
// Bot Dialogs
//=========================================================
bot.dialog('/', new builder.IntentDialog()
.matches(/^hello/i, "/hello")
.matches(/^data/i, "/data")
.matches(/^logout/i, "/logout")
.onDefault((session, args) => {
session.endDialog("I didn't understand that. Try saying 'data'.");
})
);
bot.dialog("/hello", (session, args) => {
session.endDialog("Hello. I can help you get information from my service. Try saying 'data'.");
});
bot.dialog("/data", [].concat(
ba.authenticate("auth0"),
function(session, results) {
// --------- code omitted
// call my api behind gateway using jwt token... return data.
// --------- code omitted
// let's just log the user if we get here for now
var user = ba.profile(session, "auth0");
console.log(user)
));
bot.dialog("/logout", [
(session, args, next) => {
builder.Prompts.confirm(session, "are you sure you want to logout");
}, (session, args) => {
if(args.response) {
ba.logout(session, "auth0");
session.endDialog("you've been logged out.");
} else {
session.endDialog("you're still logged in");
}
}
]);
//start the server
server.listen(PORT, function () {
console.log('%s listening to %s', server.name, server.url);
});
OK,
Took me a bit but I figured out what I was doing wrong.
First, I needed to add a code page. Then, configure BotAuthenticator
to redirect to to it at the end of the authentication phase.
server.get('/code', restify.serveStatic({'directory': path.join(__dirname, 'public'), 'file': 'code.html'}));
server.get('/styles.css', restify.serveStatic({'directory': path.join(__dirname, 'public'), 'file': 'styles.css'}));
var ba = new botauth.BotAuthenticator(server, bot, {
// ...
successRedirect: '/code', // required to redirect to code page.
//..
}).
See also the evernote and aadv2 examples: The takeaway here is that styles.css
from the Evernote Example upper-cases the magic code...so when it is C&P'd, you have to lower case it to work.
After I got passed that, I experimented with sessions. I finally settled on the easy route of passing session: true,
to the BotAuthenticator. However, since I was not creating a user in the Strategy callback, profile.user
was always null preventing the magic
because profile.user.id
is required to be non-null for 'magic' to happen. I simply created a profile.user
in the callback and added the information I needed from the profile (likely though I will use Auth0 rules to limit this in future).
I'll post back a link when I clean up the example and have instructions for creating the Auth0 tenants and clients.
Please feel free to close this.
P.S. really liking the library. We are beginning to expose API data with it, so excited to see where it goes.