nuxt-auth-utils
nuxt-auth-utils copied to clipboard
LDAP Integration
Are there any plans for LDAP auth? This feature is relevant for Microsoft Windows based infrastructure (Windows Domain Controller), mostly on-prem.
It is still widely used in Enterprise, though rather legacy compared to SAML and OAuth. I think this might make Nuxt more compelling for these larger organizations (even schools etc.)
thoughts?
I have zero knowledge on LDAP auth actually, do you have any resources to explain it?
I have zero knowledge on LDAP auth actually, do you have any resources to explain it?
I'm not an expert on this topic, but I found these articles:
https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol
https://www.okta.com/identity-101/what-is-ldap/#:~:text=Lightweight%20directory%20access%20protocol%20(LDAP,conversation%20on%20a%20new%20printer.
https://www.redhat.com/en/topics/security/what-is-ldap-authentication
https://jumpcloud.com/blog/what-is-ldap-authentication
I also found this library: https://www.npmjs.com/package/ldap-authentication
LDAP auth would allow users to use the same account they already use with windows, microsoft outlook etc. Big plus for internal apps and getting approval from sys admins
I am using the ldapts library with this module
Here's a stripped down version of how I'm doing it
/server/api/auth/login.post.js
import { Client } from 'ldapts';
export default defineEventHandler(async (event) => {
const { username, password } = await readBody(event)
const client = new Client({
url: 'ldap://mydomaincontroller.mydomain.local',
});
try {
await client.bind(`mydomain\\${username}`, password);
loginSuccess = true;
}
catch {
throw createError({
statusCode: 403,
statusMessage: 'Invalid Username or Password',
})
}
finally {
await client.unbind();
}
await setUserSession(event, {
user: {
...
},
})
return sendNoContent(event)
})
Hi there, Thank you for the solution. Could you guide me a bit more on this ? I havent been abble to make it work for now. Best
I was able to get SAML working for me, using the saml2-js library. Here's what I did, in case this is helpful to anyone else. There are 2 pieces, one with the options that you get from your SAML provider (this was Microsoft Entra in my case), and the second one being a middleware that captures all of the /saml routes and directs them to the correct page. So to login, I would go to localhost:3000/saml/login, and to logout I would go to localhost:3000/saml/logout. Hopefully this is helpful to someone else as it took me a while to figure out!
npm install saml2-js
/server/helpers/saml.model.ts
import saml2 from 'saml2-js'
import fs from 'fs'
const spOptions = {
entity_id: "ENTITY_ID",
private_key: fs.readFileSync("MY_KEY.key").toString(),
certificate: fs.readFileSync("MY_CERT.pem").toString(),
assert_endpoint: "http://localhost:3000/saml/assert"
}
export const sp = new saml2.ServiceProvider(spOptions)
const idpOptions = {
sso_login_url: "MY_LOGIN_URL",
sso_logout_url: "MY_LOGOUT_URL",
certificates: [fs.readFileSync("SAML_CERT.cer").toString()],
allow_unencrypted_assertion: true
}
export const idp = new saml2.IdentityProvider(idpOptions)
/server/middleware/saml.ts
import { sp, idp } from '../helpers/saml.model'
import { H3Event } from 'h3'
import { sendRedirect } from 'h3'
import { Users } from '../api/users.model'
export default defineEventHandler(async (event: H3Event) => {
const urlObj = getRequestURL(event)
const temp = urlObj.pathname.substring(1).split('/')
if (temp[0] === 'saml') {
switch (temp[1]) {
case 'login':
return new Promise((resolve, reject) => {
sp.create_login_request_url(idp, {}, function(err: any, login_url: string, request_id: string) {
if (err != null) {
throw createError({
statusCode: 500,
statusMessage: err,
})
}
return resolve(sendRedirect(event, login_url))
})
})
break
case 'assert':
const request_body = await readBody(event)
return new Promise((resolve, reject) => {
sp.post_assert(idp, { request_body }, async function (err: Error, saml_response: any) {
if (err != null) {
throw createError({
statusCode: 500,
statusMessage: err.message,
})
}
// Save name_id and session_index for logout
// Note: In practice these should be saved in the user session, not globally.
const roles = <string[]>[]
const { name_id, attributes: samlUser } = saml_response.user
// See if this user exists in the Users collection
const res = await Users.find({ email: samlUser.email })
if (res.length === 0) {
// If not, add them
const res = await Users.create(samlUser)
user._id = res._id
} else {
// If so, update them
user._id = res[0]._id
await Users.updateOne({ _id: res[0]._id }, samlUser)
}
await setUserSession(event, {
user,
loggedInAt: Date.now(),
})
resolve(sendRedirect(event, "http://localhost:3000"))
})
})
break
case 'logout':
break
case 'manifest.xml':
setResponseHeaders(event, {
"Content-Disposition": `attachment; filename=test.xml`,
"Content-Type": "text/xml; charset=latin1",
})
return sp.create_metadata()
break
}
}
})