Account linking from regular web app

I want to implement user-initiated account linking as explained here. On a configuration page, the authenticated user will be able to click on a Link another account button, authenticate through another connection then be redirected back to the application that will call the /api/v2/users/{id}/identities endpoint.
The application is an Express app and I found this example which is totally outdated and doesn’t run. I cannot figure how to adapt it to a newer version of express-openid-connect.
Can somebody help me understand how to set up my Express app so I can retrieve a secondary JWT for a user, while keeping the original access token to call the management API?
Thanks in advance for any advice!

Hi @nathan.b

Welcome back to the Auth0 Community!

i am sorry about the late reply to your post.

Besides the documentation on Server-side Account Linking tutorial provided and the GitHub example, we do no offer any other out-of-the-box solution regarding integrating this feature for your applications. It is specified on the documentation page that:

Your app must provide the UI, such as a Link accounts button on the user’s profile page.

I would highly recommend to use the Account Linking Extension or link the account using Actions.

If you have any other questions regarding the matter or found a solution yourself to this issue, feel free to leave a reply or post on the community!

Kind Regards,
Nik

Thank you for this information. I had already tried what you suggested and it is not really of any help regarding my use case.

I know that we are supposed to provide the UI, and that was what my initial question was about. I was referring to a specific example for an Express app that is no longer relevant and should either be updated with the newest Auth0 SDK for Node.js, or simply removed as not to confuse other developers facing the same issues as I did.

As for your recommendations, I would not recommend the Account Linking Extension which seems to rely on Rules, a deprecated feature. Plus the UI is not consistent with the new Universal Login we are using, unless we bother with customizing it so it does not look suspicious to our end-users, or go back with classic login.

Actions are a good solution, although as per the sample code provided in the documentation, it still requires to implement some sort of custom external API to handle the redirection flow that will make the request to the management API for linking accounts. This is fine because not as complex as having to set up a whole web app and handling two authentications with Express.

Hi again @nathan.b

Thank you for your feedback. I understand that that the provided solutions have a downside regarding implementing them to your use case. I will reach out internally regarding the outdated Account Linking Sample for Regular Web Apps to see if it can be updated to todays standards, removed as per your suggestion or update the documentation for Server-Side Account linking.

If you have any other questions or input on the matter, you can always reply or post on the community page.

Kind Regards,
Nik

Thank you @nik.baleca!

For others who might struggle with setting up account linking with a custom UI using Express.js, here is how I managed to implement a behavior similar to the example with Auth0 SDK (auth0@4.4.0) and express-openid-connect@2.17.1.

Main app code:

import express from 'express';
import oidc from 'express-openid-connect';

app.use(oidc.auth({
    authRequired: false, // Important for bypassing auth on /link route
    secret: process.env.SECRET,
    baseURL: process.env.BASE_URL, // Redirect URL
    clientID: process.env.CLIENT_ID,
    issuerBaseURL: process.env.ISSUER,

    // Extract and store user in session (`req.appSession`)
    afterCallback: (_req, _res, session) => {
        session.user = jwtDecode(session.id_token);
        return session;
    }
}));

// Authenicated routes
app.use('/', oidc.requiresAuth(), homeRouter);

// Link route must not be authenticated with main OIDC config
app.use('/link', linkRouter);

// (...)

You may have as many authenticated routes using the requiresAuth() middleware. One of these routes must lead the end-user to make a POST call to /link. I used an HTML form rendered in a template with a single button. Here’s how it should be handled by the linkRouter:

import express from 'express';
import oidc from 'express-openid-connect';

const linkRouter = express.Router();

// Secondary account OIDC config
linkRouter.use(oidc.auth({
    authRequired: false,
    secret: process.env.SECRET,
    baseURL: process.env.BASE_URL + '/link', // Remember to register with your Auth0 application
    clientID: process.env.CLIENT_ID,
    issuerBaseURL: process.env.ISSUER,
    authorizationParams: {
        connection: 'connection-name', // Set whatever connection the secondary account is authenticated with
    },
    session: {
        name: 'linkSession' // Save data to another session, otherwise appSession will be overwritten
    },
    routes: {
        login: false,
        logout: false,
        postLogoutRedirect: process.env.BASE_URL // After linking, go back to home page
    }
}));

// POST call from end-user (eg. through HTML form)
linkRouter.post(async (req, res, next) => {
    // At this point user should already have authenticated with primary account,
    // so a cookie has been sent and appSession has been populated.
    if (!req.appSession?.user?.email) {
        next(new Error('Invalid session'));
    } else {
        // Redirect user for authentication with target account
        await res.oidc.login({
            returnTo: '/link',
            authorizationParams: {
                login_hint: req.appSession.user.email
            }
        });
    }
});

// GET call after user has authenticated with secondary account
linkRouter.get(oidc.requiresAuth(), async (req, res, next) => {
    const primaryUserId = req.appSession.user.sub; // appSession should be set from cookie related to main account session
    const targetUserId = req.oidc.idTokenClaims.sub;
    try {
        // Link accounts 
        await this.auth0Service.linkUser(primaryUserId, targetUserId);
    } catch (e) {
        next(e);
    } finally {
        // Log out from secondary account, navigate back to home
        await res.oidc.logout();
    }
});

export default linkRouter;

There might be a better solution, I’m open to critics. In the meantime this is the app we use to allow our end-users to link their accounts.

Thank you for sharing your implementation with us @nathan.b!

Kind Regards,
Nik

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.