Custom auth - problem with getcurrentUser and protected routes

I’m implementing MSAL.js to make an Azure Active Directory login.
The login itself works great, im getting a successful handshake.
But i’m unable to protect routes, and for some reason, getcurrentUser isnot getting called at all.
In addition, when trying to go to a “protected route”, im getting http://localhost:8910/?redirectTo=/admin in the URL, and a blank white page.

im currently not trying to write or read anything from the db.

index.js

const msalConfig = {
  auth: {
    clientId: 'xxxx',
    authority:
      'xxxx',
    redirectUri: 'http://localhost:8910/admin',
  },
  cache: {
    cacheLocation: 'sessionStorage', // This configures where your cache will be stored
    storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge
  },
}


const authClient = new Msal.UserAgentApplication(msalConfig)

ReactDOM.render(
  <FatalErrorBoundary page={FatalErrorPage}>
    <AuthProvider client={authClient} type="custom">
      <RedwoodProvider>
          <Routes />
      </RedwoodProvider>
    </AuthProvider>
  </FatalErrorBoundary>,
  document.getElementById('redwood-app')

api/src/lib/auth.js

export const getCurrentUser = async (decoded, { type, token }) => {
  console.log('get user') // < not called
}

node_modules/@redwoodjs/auth/dist/authClients/custom.js
“use strict”;

var _Object$defineProperty = require("@babel/runtime-corejs3/core-js/object/define-property");

_Object$defineProperty(exports, "__esModule", {
  value: true
});

exports.custom = void 0;

var custom = function custom(authClient) {
  return {
    type: 'custom',
    client: authClient,
    // restoreAuthState: async () => {
    //   window.history.replaceState(
    //     {},
    //     document.title,
    //     '/admin'
    //   )
    // },
    login: function login() {
       var loginRequest = {
    scopes: ['openid', 'profile', 'User.Read'],
  }
console.log('login')
authClient
      .loginPopup(loginRequest)
      .then((loginResponse) => {
        console.log('id_token acquired at: ' + new Date().toString())
        console.log('loginResponse', loginResponse)

        if (authClient.getAccount()) {
          console.log(authClient.getAccount()) // << working
        }
      })
      .catch((error) => {
        console.log(error)
      })
    },
    logout: function logout() {
console.log('log out')
authClient.logout() // << working
    },
    getToken: function () {
console.log('get token')
return 'string'
    }(),
    getUserMetadata: function () {
console.log('getUserMetadata')
  return authClient.getAccount() || null
    }()
  };
};

exports.custom = custom;

@NiviJah Hi.

Does your api graphql function include getCurrentUser in createGraphQLHandler?

export const handler = createGraphQLHandler({
  getCurrentUser,
schema: makeMergedSchema({
    schemas,
    services: makeServices({ services }),
  }),
  db,
})

yes

api/src/functions/graphql.js

import {
  createGraphQLHandler,
  makeMergedSchema,
  makeServices,
} from '@redwoodjs/api'
import importAll from '@redwoodjs/api/importAll.macro'

import { getCurrentUser } from 'src/lib/auth.js'

import { db } from 'src/lib/db'

const schemas = importAll('api', 'graphql')
const services = importAll('api', 'services')

export const handler = createGraphQLHandler({
  schema: makeMergedSchema({
    schemas,
    services: makeServices({ services }),
  }),
  getCurrentUser,
  db,
})

It looks like your redirect is coming from MSAL.

Here’s my suggestion

  1. Setup auth with redwood following this guide https://redwoodjs.com/tutorial/authentication. Don’t include MSAL just yet.
  2. Try to move your setup to use custom auth (no need for a db, just change the providers, and update your getCurrentUser function)
  3. Once you have an understanding of how redwood is doing auth, then add your MSAL code

PS it looks like you copied the code from my old forum post on custom github auth. Please note that you don’t need to modify node_modules anymore.

1 Like

appreciate your answer @danny

  1. I already successfully implemented auth0
  2. getCurrentUser is not being called at all
  3. I don’t believe the redirect is coming from MSAL, every “protected route” I enter in the url is being replaced with ?redirectTo=/pageName and a blank page

Just FYI getCurrentUser is only called from services i.e. the api side, not on the frontend. And potentially when passing the auth token, and the auth-provider header. (not 100% sure, haven’t looked at it in a while)

Maybe this’ll help?

If you look at your MSAL config you have this line: redirectUri: 'http://localhost:8910/admin',

Update: RW Does have redirectTo now, however the problem would look to me to still be in auth client setup as peterp suggest below

I think the problem is here:

You’re passing the raw “authClient” to redwood without mapping the values. You’ll need to map the “shape” of Msal to match that which Redwood expects, what I mean by this is that Redwood expects your client object to have the following named functions that return the correct values:

You can see the implementation of various other clients over here: https://github.com/redwoodjs/redwood/tree/main/packages/auth/src/authClients

As an example; if new Msal returns an object that has a LogInWithPopUp method, you would have to map this method like this:

const originalAuthClient = new Msal.UserAgentApplication(msalConfig)

const myAuthClient = {
    type: 'custom',
    client: originalAuthClient,
    login: originalAuthClient.LogInWithPopUp,
   /* ...  implement getToken, logout, getUserMetadata */
}
1 Like

Thank you !
that’s something I haven’t tried before.
will update

Trying to keep stuff as simple as possible,
still getting null data, do I need to implicitly set isAuthenticated somehow ?

web/src/index.js
const authClient = new Msal.UserAgentApplication(msalConfig)

const getMSALAccount = () => {
  if (authClient.getAccount()) {
    return authClient.getAccount()
  }
}

const MSALAuthClient = {
  type: 'custom',
  client: authClient,
  login: () => authClient.loginRedirect(loginRequest),
  logout: () => authClient.logout(),
  getToken: () => getMSALAccount,
  getUserMetadata: () => getMSALAccount,
}

ReactDOM.render(
  <FatalErrorBoundary page={FatalErrorPage}>
    <AuthProvider client={MSALAuthClient} type="custom">

node_modules/@redwoodjs/auth/dist/authClients/custom.js
_Object$defineProperty(exports, “__esModule”, {
value: true
});

exports.custom = void 0;

var custom = function custom(MSALAuthClient) {
  return {
    type: 'custom',
    client: MSALAuthClient,
    login: () => MSALAuthClient.login(),
    logout: () => MSALAuthClient.logout(),
    getToken: () => MSALAuthClient.getToken(),
    getUserMetadata: () => MSALAuthClient.getUserMetadata(),
  }
}

exports.custom = custom;

This code doesn’t look correct. You “if block” is executing the function. Also, isn’t getAccount() a promise?

This was taken from the docs, looks like the official way to do it

I am not sure if thats a promise or not.

Ah, well, looking at those same docs they reference: myMSALObj.loginPopup(loginRequest) which is a promise; is myMSALObj.login also valid?

@peterp I wonder if there should be a new topic/thread/GH issue where we track other AuthProviders to support out of the box based on what the community needs.

For example:

  • AAD
  • Apple
  • Twitter
  • etc

… or a more generic OAuth similar to NextAuth.js?

NextAuth.js includes built-in support for signing in with Apple, Auth0, Google, Battle.net, Box, AWS Cognito, Discord, Facebook, GitHub, GitLab, Google, Open ID Identity Server, Mixer, Okta, Slack, Spotify, Twitch, Twitter and Yandex.

I feel like MSAL.js specially should have been super easy
the handshake itself works great, super straightforward and minimal code.
it’s the integrations with RW i’m struggling with

some refactoring

const authClient = new Msal.UserAgentApplication(msalConfig)

const makeLogin = () => {
  authClient
    .loginPopup(loginRequest)
    .then((loginResponse) => {
      console.log('loginResponse', loginResponse)
      // navigate(routes.admin())
    })
    .catch(function (error) {
      console.log(error)
    })
}

const getMSALAccount = async function (authClient) {
  const data = await authClient.getAccount()
  console.log('data', data)
  return data
  // return authClient.getAccount() || 'no data returned'
}

const MSALAuthClient = {
  type: 'custom',
  client: authClient,
  login: () => makeLogin(),
  logout: () => authClient.logout(),
  getToken: getMSALAccount(authClient),
  getUserMetadata: getMSALAccount(authClient),
}

ReactDOM.render(
  <FatalErrorBoundary page={FatalErrorPage}>
    <AuthProvider client={MSALAuthClient} type="custom">

node_modules/@redwoodjs/auth/dist/authClients/custom.js

var custom = function custom(MSALAuthClient) {
  return {
    type: 'custom',
    client: MSALAuthClient,
    login: () => MSALAuthClient.login(),
    logout: () => MSALAuthClient.logout(),
    getToken: MSALAuthClient.getToken,
    getUserMetadata: MSALAuthClient.getUserMetadata,
  }
}

exports.custom = custom;

1 Like

Supporting next-auth will be great