0
votes

hoping someone can help a noob with some async javascript issues.

I am attempting to secure a GraphQL server instance using Passport and the passport-azure-ad strategy. I can see from logs that the incoming Access Token is validated correctly and all of the passport code is working as expected.

const passport = require('passport');
const OIDCBearerStrategy = require('passport-azure-ad').BearerStrategy;

...

// Setting up the Strategy and verify function
const bearerStrategy = new OIDCBearerStrategy(options, (token, done) => {
  if (!token.scp.includes('check-this-scope')) {
    return done(null, false, { message: 'User not authorized to access resource' });
  }
  return done(null, token);
});

passport.use('oauth-bearer', bearerStrategy);

...

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: async (context) => {
    passport.authenticate('oauth-bearer', (authErr, authUser, authInfo) => {
      if (!authUser) context.authErr = authErr || authInfo;
      else context.user = authUser;
    })(context.req, context.res);
    return context;
  },
});

The authErr and authUser params are correctly being passed to the custom callback, however the context is returned before the callback is executed.

Ideally I would like to if (!user) throw new AuthenticationError('you must be logged in'); in the context block if the user is not available, however due to the asynchronous nature of the code I don't have access to it.

How can I make the code in the context wait until the custom passport.authenticate callback has executed? Or alternatively is there a better way for me to do this? I am new to apollo server, passport and even working with node, so I wouldn't be surprised if I was doing something wrong.

Right now it seems that my setting of context.user isn't even available in the context delivered to the resolvers.

Thanks in advance.

1

1 Answers

1
votes

Basically, you need to "promisify" you authenticate request:

const authOauthBearer = ({ req, res }) => new Promise((resolve, reject) => {
  passport.authenticate('oauth-bearer', (authErr, authUser, authInfo) => {
    if (!authUser) reject(authErr || authInfo);
    else resolve(authUser);
  })(req, res);
});

and then use it in the context like this:

context: async (context) => {
  try {
    const { user } = authOauthBearer(context);
    context.user = user;
  } catch(error) {
    // you may want to escalate the auth issue to the client so you can just throw:
    // throw error;

    // or you can store the auth error in the context
    context.authError = error;
  }
  return context;
}