Search code examples
azurenext.jsldap

Azure LDAP Authentication Connection Refused On Cloud Server or Other Desktops


I am new to Azure LDAP, I am creating Nextjs Site in which user can Authenticate With their Email Password details that are created on Microsoft Entra ID (Azure Active Directory). it's Authenticate fine on local http://localhost:3000 user can able to SignIn but when I uploaded my nextjs site on digital ocean cloud hosting it is generating and error and refusing to connect and When I try to use on other laptops it also refusing to connect am I missing something or I need to generate Certificate for cloud hosting?

Here is my Code that attemp to connecting to LDAP

import { NextResponse } from "next/server";
import ldap from "ldapjs";
import fs from "fs";
import path from "path";
import mysql from "mysql2/promise";
import jwt from "jsonwebtoken";


export async function POST(req) {

    if (req.method === 'POST') {
        // const token = req.cookies.get("token");
        // if (!token) {
        //     return NextResponse.json({ success: false, "error": "Authorization Failed!" }, { status: 401 })
        // }
        // const SECRET_KEY = process.env.JWT_SECRET || 'your_secret_key';
        // const decodedToken = jwt.verify(token["value"], SECRET_KEY);
        // let adminId = decodedToken.admin_id == 0 ? decodedToken.id : decodedToken.admin_id;//if already admin than it's id if 
        //not admin then employee's amdin id
        const { username, email, password, token = '0' } = await req.json();

        var db = null;
        let ldapSettings = null;
        try {
            db = await mysql.createConnection({
                host: process.env.DB_HOST,
                user: process.env.DB_USER,
                password: process.env.DB_PASSWORD,
                database: process.env.DB_DATABASE,
            })

            const [ldapSettingsData] = await db.execute(`SELECT * from ldap_settings WHERE login_token='${token}'`);
            if (ldapSettingsData.length == 0) {
                return NextResponse.json({ success: false, "error": "LDAP Settings not found!" }, { status: 400 })
            }

            if (!ldapSettingsData[0]['ldap_enabled']) {
                return NextResponse.json({ success: false, "error": "LDAP not available, Contact admin for LDAP Login!" }, { status: 400 })
            }

            ldapSettings = ldapSettingsData;
        } catch (e) {
            return NextResponse.json({ success: false, "error": "Server Error Occured!" }, { status: 500 })
        } finally {
            if (db) {
                db.destroy();
            }
        }
        // console.log(username, email, password);
        let ldapCert = null;
        try {
            ldapCert = path.join(process.cwd(), 'public', "certificates", (ldapSettings?.[0]?.["ldap_cert_file"] ?? "")); //Certificate file //"aadds-cert.cer"//(ldapSettings?.[0]?.["ldap_cert_file"] ?? "")
            ldapCert = fs.readFileSync(ldapCert); // Adjust the path as needed
        } catch (e) {
            // console.log("Client Error");
            // console.log(e);
            return NextResponse.json({ error: "Certificate File not found!" }, { status: 404 });
        }

        const client = ldap.createClient({// ldap host 
            //If use_tls is checked than we use 389 port for startTLS
            url: ldapSettings[0]['ldap_server'] + ":" + (ldapSettings[0]['use_tls'] ? "389" : "636"),
            // Use LDAPS (LDAP over SSL) 636 for SSL and TLS and 389 is for STARTTLS  
            tlsOptions: {
                ca: [ldapCert], // Add the self-signed certificate to the trusted list            
                //rejectUnauthorized will enable if ldap_ssl_cert_verif = 1 and LDAP SSL certificate validation enabled from settings
                rejectUnauthorized: (ldapSettings?.[0]?.ldap_ssl_cert_verif != null && ldapSettings[0]['ldap_ssl_cert_verif'] ? false : true) // Set to true to reject invalid certificates in production
            },
            reconnect: true, // Optional: Enables auto-reconnect
        });

        // Handle connection errors
        client.on('error', (err) => {
            console.error("LDAP connection error:", err.message);
            if (err.code) {
                console.error("Error code:", err.code);
            }
            // Additional logging or actions can go here
        });

        // Handle successful connection
        client.on('connect', () => {
            console.log("Successfully connected to the LDAP server.");
        });

        // console.log("LDAP SERVER", ldapSettings[0]['ldap_server'] + ":" + (ldapSettings[0]['use_tls'] ? "389" : "636"));

        // Start a simple bind for authentication
        return new Promise((resolve) => {
            /*client.bind(`uid=${username},ou=users,dc=aadds,dc=jetxxxx,dc=com`, password, (err) => {
                let response = null;
                console.log("err",err);
                if (err) {
                    console.log(err);
                    response =  NextResponse.json({ success: false, error: "Some Error Occured" }, { status: 401 });
                } else {
                    response =  NextResponse.json({ success: true, message: "Authenticated successfully" }, { status: 200 });
                }

                // Unbind the client after the authentication process is done
                client.unbind();
                resolve(response);
            });*/



            let response = null;
            //User name and password
            // console.log("User Name:", username.trim() , " Password:" + password.trim());


            //Start STARTTLS if enabled from settings

            // Perform STARTTLS Enabled if TLS Enabled
            /*client.starttls(tlsOptions, null, (err) => {
                 if (err) {
                    console.error('Error enabling STARTTLS:', err);
                    client.unbind(); // Close the connection if an error occurs
                }
                response = NextResponse.json({ success: false, error: "STARTTLS Connection Error!" }, { status: 400 });
                resolve(response); // Ensure to resolve with response here
                return;
                console.log('STARTTLS connection established');
            });*/

            /* IF Azure Active Directory is enable than ADD OU="AADDC Users" Other Wise */
            // console.log("Binding With", typeof ("CN=" + username + `,"${ldapSettings[0]['is_azure_ad'] ? ldapSettings[0]['base_bind_dn'].replace(/ou=|OU=/g, "ou=AADDC ") : ldapSettings[0]['base_bind_dn']}"`), username, password);
            client.bind(("CN=" + username + `,${ldapSettings[0]['is_azure_ad'] ? ldapSettings[0]['base_bind_dn'].replace(/ou=|OU=/g, "ou=AADDC ") : ldapSettings[0]['base_bind_dn']}`), password, (error) => {
                // client.bind("CN=" + username + ",OU=AADDC Users,dc=aadds,dc=jetxxxxxx,dc=com", password, (error) => {
                // console.log("Error: ", error);
                if (error) {
                    // console.error("Failed")
                    // reject()
                    // console.log(error, "LDAP ERROR 101");
                    response = NextResponse.json({ success: false, error: "Invalid LDAP Credentials or LDAP Error Occured!" }, { status: 401 });
                    client.unbind();
                    resolve(response); // Ensure to resolve with response here
                    return;
                } else {
                    // console.log("Logged in")
                    // resolve({
                    //     username: credentials.username,
                    //     password: credentials.password,
                    // })

                    // Define the search options
                    const opts = {
                        filter: ldapSettings[0]['ldap_filter'].replace(/{email}/g, email).replace(/{username}/g, username),
                        scope: 'sub',
                        attributes: [],
                    }


                    // console.log("Logged in");
                    let user_exists = false;
                    client.search("CN=" + username + `,${ldapSettings[0]['is_azure_ad'] ? ldapSettings[0]['base_bind_dn'].replace(/ou=|OU=/g, "ou=AADDC ") : ldapSettings[0]['base_bind_dn']}`, opts, (err, res) => {
                        if (err) {
                            response = NextResponse.json({ success: false, error: "Search Query Error" }, { status: 401 });
                            client.unbind();
                            resolve(response); // Ensure to resolve with response here
                            return;
                        }

                        res.on('searchEntry', (entry) => {
                            user_exists = true;
                        });

                        res.on('error', (err) => {
                            console.log(err);
                            response = NextResponse.json({ success: false, error: "User finding error!" }, { status: 401 });
                            client.unbind();
                            resolve(response);
                        });

                        res.on('end', async (result) => {
                            if (user_exists) {
                                let db = null;
                                try {
                                    db = await mysql.createConnection({
                                        host: process.env.DB_HOST,
                                        user: process.env.DB_USER,
                                        password: process.env.DB_PASSWORD,
                                        database: process.env.DB_DATABASE,
                                        port: process.env.DB_PORT
                                    });

                                    const [rows] = await db.execute('SELECT id, source, admin, name from users WHERE email=? and source=?', [email, "ldap"]);

                                    let user_id = '';
                                    let adminId = ldapSettings[0]['admin_id'];
                                    let user_dp = '';
                                    let user_name = '';

                                    if (rows.length === 0) {
                                        //User not exists in the system, Add New User //admin id will the id whose id use for intergration with LDAP
                                        await db.execute("INSERT INTO users SET admin=?, name=?, email=?, source=?, role=?", [adminId, username, email, 'ldap', 'employee']).then(([result]) => {
                                            user_id = result.insertId; // Get the inserted ID
                                        });
                                        user_name = username;
                                    } else {
                                        //User already exists in the system
                                        user_id = rows[0]['id'];
                                        adminId = rows[0]['admin'];
                                        user_dp = (rows[0]?.dp ?? "");
                                        user_name = (rows[0]?.name ?? "");
                                    }

                                    const SECRET_KEY = process.env.JWT_SECRET || 'your_secret_key';
                                    // JWT token generation
                                    const token = jwt.sign(
                                        { id: user_id, admin_id: adminId, name: user_name, email: email, source: "LDAP", dp: (user_dp ?? ''), role: 'employee'}, // Payload: user data
                                        SECRET_KEY, // Secret key
                                        { expiresIn: process.env.JWT_TOKEN_EXPIRES || '24h' } // Token expiration time
                                    );

                                    // Set token in a cookie (httpOnly and secure for better security)
                                    const response = NextResponse.json({
                                        message: 'Authenticated successfully!',
                                    }, { status: 200 });

                                    response.cookies.set('token', token, {
                                        httpOnly: true, // Prevent client-side access to the token
                                        maxAge: process.env.COOKIES_EXPIRES_SECONDS || (24 * 60 * 60), // 1 hour expiration
                                        path: '/',
                                    });

                                    client.unbind();
                                    resolve(response);
                                } catch (error) {
                                    console.error('Error logging in:', error);
                                    response = NextResponse.json({ error: 'Error Occured within Server!' }, { status: 500 });
                                    client.unbind();
                                    resolve(response);
                                } finally {
                                    if (db) {
                                        db.destroy();
                                    }
                                }
                                // response = NextResponse.json({ success: true, message: "Authenticated successfully!", status: result.status }, { status: 200 });
                            } else {
                                response = NextResponse.json({ success: false, error: "User not found!" }, { status: 401 });
                            }
                            client.unbind();
                            resolve(response); // Resolve response here if an error occurs
                        });
                    });

                    // response = NextResponse.json({ success: true, message: "Authenticated successfully" }, { status: 200 });
                }
            });
        });
    } else {
        // console.log("Only POST requests are allowed");
        return NextResponse.json({ error: 'Only POST requests are allowed' }, { status: 405 });
    }
}

Getting Error refuse to connect


Solution

  • Azure LDAP Authentication Connection Refused On Cloud Server or Other Desktops

    The issue is related to network access, certificates, or configuration.

    • Localhost works because the network allows access to the LDAP server. check that the DigitalOcean server or other devices can connect to the LDAP server.

    • Confirm the LDAP server’s firewall rules allow connections from the IP address of the DigitalOcean host. Contact the IT/network team to whitelist the public IP of the DigitalOcean server.

    • Test connectivity to the LDAP server from the DigitalOcean server using a tool like telnet or openssl:

    telnet your-ldap-server 389
    openssl s_client -connect your-ldap-server:636
    
    

    For secure connections (port 636 or STARTTLS on port 389), the LDAP server requires a trusted certificate. If the LDAP server uses a self-signed certificate, you must include the certificate on the DigitalOcean server. check the code correctly loads the certificate via tlsOptions:

    azure image

    tlsOptions: {
            ca: [fs.readFileSync('/path/to/certificate.pem')],
            rejectUnauthorized: true,
        }
    
    
    • If the certificate is missing or invalid, your connection will fail. Double-check the certificate file path and format.

    local

    Adding user to the database:

    database