node.jsexpressoauth-2.0adfs

ADFS2016 oauth2 confidential app missing user informations


I am trying to authenticate users into my webapp using my company's ADFS 2016 server.

Using nodejs and express. I am able to authenticate properly but I do not receive any user profile information. I need at least email, name and surname.

Here is the relevant part of the code:

app.get("/login", (req: Request, res: Response) => {
    const redirectUri = `http://localhost:${PORT}/redirect`; // Redirect URI after authentication
    const queryParams = new URLSearchParams({
        response_type: "code",
        client_id: CLIENT_ID,
        redirect_uri: redirectUri,
        resource: "urn:microsoft:userinfo",
        scope: "openid"
    });

    res.redirect(`${AUTHORITY}/oauth2/authorize?${queryParams}`);
});

app.get("/redirect", async (req: Request, res: Response) => {
    const code = req.query.code;
    const data = {
        client_id: CLIENT_ID,
        client_secret: CLIENT_SECRET,
        grant_type: "authorization_code",
        code: code,
        redirect_uri: `http://localhost:${PORT}/redirect`
    };

    try {
        const response = await axios.post(TOKEN_ENDPOINT, data, {
            headers: {
                "Content-Type": "application/x-www-form-urlencoded"
            }
        });
        const accessToken = response.data.access_token;

        const userInfo = await axios.get(USERINFO_ENDPOINT, {
            headers: {
                Authorization: `Bearer ${accessToken}`
            }
        });

        res.send("Authenticated successfully:" + JSON.stringify(userInfo.data, null, 2));

    } catch (error) {
        console.error("Error occurred during authentication:", error);
        res.status(500).send("Failed to authenticate");
    }
});

app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

Currently the returned token has the following properties:

{
    "aud": "urn:microsoft:userinfo",
    "iss": "http://******/adfs/services/trust",
    "iat": 1709024014,
    "exp": 1709027614,
    "apptype": "Confidential",
    "appid": "********",
    "authmethod": "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
    "auth_time": "2024-02-27T07:02:54.533Z",
    "ver": "1.0",
    "scp": "openid",
    "sub": "*******"
}

And the userInfo object only contains a sub property.

{ "sub": "**********" }

What do I need to change to request those informations?


Solution

  • In the end, there was no need to get the USERINFO_ENDPOINT.

    Everything is included in the accessToken.

    The problem was the property resource: "urn:microsoft:userinfo". Deleting it allows to get email, first name and last name.

    app.get("/login", (req: Request, res: Response) => {
        const redirectUri = `${DOMAIN}/redirect`; // Redirect URI after authentication
        const queryParams = new URLSearchParams({
            response_type: "code",
            client_id: CLIENT_ID,
            redirect_uri: redirectUri,
            scope: "openid"
        });
    
        res.redirect(`${AUTHORITY}/oauth2/authorize?${queryParams}`);
    });
    
    app.get("/redirect", async (req: Request, res: Response) => {
        const code = req.query.code;
        const data = {
            client_id: CLIENT_ID,
            client_secret: CLIENT_SECRET,
            grant_type: "authorization_code",
            code: code,
            redirect_uri: `${DOMAIN}/redirect`
        };
    
        try {
            const response = await axios.post(TOKEN_ENDPOINT, data, {
                headers: {
                    "Content-Type": "application/x-www-form-urlencoded"
                }
            });
            const accessToken = response.data.access_token;
    
            const decodedToken = ValidateToken(accessToken);
    
            res.send(
                "Welcome " +
                    decodedToken.unique_name +
                    " " +
                    decodedToken.family_name +
                    " " +
                    decodedToken.primarysid
            );
        } catch (error) {
            console.error("Error occurred during authentication:", error);
            res.status(500).send("Failed to authenticate");
        }
    });
    

    For reference, below is the interface of the token:

    interface AdfsJwt {
        aud: string;
        iss: string;
        iat: number;
        exp: number;
        apptype: string;
        appid: string;
        authmethod: string;
        auth_time: string;
        ver: string;
        scp: string;
        family_name: string; // Last name
        primarysid: string; // Email
        unique_name: string; // First name
    }