-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[api] add Microsoft strategy to auth module (single sign-on) #3453
Conversation
Removed vultr server and associated DNS entries |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for bearing with me whilst I got through this. Will give it another pass through once top level await takes away a lot of the changes here 👍
Code looking great - easy to follow and clear!
8ffa211
to
ac7a60e
Compare
ac7a60e
to
d9f76ad
Compare
Rebased this PR onto #3528 - full explanation given in that PR description. |
380c91c
to
64b37d2
Compare
d67e62f
to
92013f2
Compare
clean up code: add comments, remove logs, improve naming etc add exemplar Microsoft auth environment variables add Microsoft URLs to CORS allow list
630caed
to
9f08497
Compare
9f08497
to
a918020
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some notes
const customPassport = new passport.Passport(); | ||
|
||
// instantiate Microsoft OIDC client, and use it to build the related strategy | ||
const microsoftIssuer = await Issuer.discover(MICROSOFT_OPENID_CONFIG_URL); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fetches config from Microsoft's .well-known
address
import * as Middleware from "./middleware.js"; | ||
import * as Controller from "./controller.js"; | ||
|
||
const router = Router(); | ||
export default (passport: Authenticator): Router => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have to pass the passport
instance through to the middleware for these routes, since they rely on it already being instantiated, which in turn is a blocking process, so instead export a factory function as default.
return { | ||
client_id, | ||
client_secret: process.env.MICROSOFT_CLIENT_SECRET!, | ||
redirect_uris: [`${process.env.API_URL_EXT}/auth/microsoft/callback`], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this has to match one of the redirect URI configured in the PlanX Azure application
i.e. https://api.editor.planx.dev/auth/microsoft/callback
for staging
and https://api.editor.planx.uk/auth/microsoft/callback
for prod
cb(null, obj); | ||
}); | ||
// equip passport with auth strategies early on, so we can pass it to route handlers | ||
const passport = await getPassport(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the fabled top-level await!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And a query requiring some typescript tekkers
Merging to |
🎫 Ticket: https://trello.com/c/owvV9UZh
Summary
This PR adds the ability to sign into PlanX using a Microsoft account (personal or organisational), which most users (working in local government) are more likely to have to hand as compared to a Google account.
We make use of the existing passportjs implementation, adding a new
microsoft-oidc
strategy which leverages openid-client, which we consider reliable thanks to certification by OpenID themselves.Since this package has some asynchronous behaviour for which the best code pattern involved using top-level awaits, this project induced some bonuses in
api.planx.uk
, namely adopting ESM throughout (#3464), which in turn required a migration to Vitest (#3555). We also took the opportunity to bump passportjs to latest (#3502).See also #3221, which I used as a development/demo pizza, and has a much more granular commit structure.
TODO
We will address anything not included here in further PRs.
Jest/ESM issue ✅
The stubborn issue here is that Jest (or
ts-jest
, the transformer we're using to transpile.ts
files into.js
files that Jest can read) will not treat files as ES modules (despite following docs to the letter), and hence throws errors like the following:I examine some of the background for this in #3528,
onto which this PR is chained.Below is a non-exhaustive list of possible approaches to this problem, which I've marked complete if they've been fully explored and not provided a solution:
ts-jest
preset instead of manual configalpha5
includes a fix which purports to 'allow Node16/NodeNext/Bundler moduleResolution in project's tsconfig', which seems relevantjest.config.js
__dirname
now that API is usingesnext
#3507 - doing so revealsReferenceError: await is not defined
, which only adds to our case that modules are not being treated as ESMverbatimModuleSyntax
(didn't resolve issue, but was edifying - see [api] implement verbatim module syntax in tsconfig #3528)ts-jest
with@swc/jest
orbabel-jest
(failing examples inapi-add-microsoft-sso-swc
andapi-add-microsoft-sso-babel
respectively) - could include using multiple transformers, e.g. babel for.js
, ts-jest for.ts
.mjs
/.mts
file endings (not a desirable outcome)Worth noting that even Jest advises that ESM support is experimental, and indeed their issue tracking this is very much still open. This could suggest we should lean to some of the latter, more climb down-y options.
🏁 In the end we abandoned Jest in favour of Vitest, which is much more ESM-friendly! See #3555 :)