Okta Redirects to Dashboard Instead of My App (SP) After "Sign in with Google"

I’m using a custom SAML login flow in my app with passport-saml and MultiSamlStrategy (Node.js/NestJS). Each org can upload its own IdP metadata (Okta, Microsoft, onelogin), and everything works fine — except in one edge case with Okta:

If the user starts SAML login from my app (SP), they’re redirected to the Okta login page.

If they log in with email/password, they’re correctly redirected back to my app.

But if they click “Need help signing in?” → “Sign in with Google”, they get authenticated, but Okta redirects them to the Okta dashboard, not back to my app.

I suspect this is because the Google login bypasses the original SAML request and doesn’t preserve the RelayState.

Is this expected behavior? How can I force users to complete the SAML flow or disable the Google login option entirely to avoid this?
my saml.strategy.service.ts file:

Blockquote

import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Request } from 'express';
import * as fs from 'fs';
import { MultiSamlStrategy, SamlConfig } from 'passport-saml';
import { Config } from 'src/config';
import { DatabaseService } from 'src/database/database.service';

@Injectable()
export class SamlStrategy extends PassportStrategy(MultiSamlStrategy, 'saml') {
    constructor(private readonly databaseService: DatabaseService) {
        super({
            private_key: fs.readFileSync('./certs/sp-private-key.pem'), // You should generate this based on your system's requirements
            certificate: fs.readFileSync('./certs/sp-certificate.pem'), // You can use a self-signed certificate or a public key
            assert_endpoint: Config.SAML.CALLBACK_URL,
            passReqToCallback: true, // Ensures we can access the request inside `getSamlOptions`,
            getSamlOptions: async (req: Request, done) => {
                try {
                    // let existingRelayState = {};
                    const relayState = req?.body?.RelayState
                        ? JSON.parse(decodeURIComponent(req?.body?.RelayState))
                        : {};

                    console.log('relayState parsed', relayState);
                    // console.log('req', req?.query);
                    const email =
                        req.body?.email || req.query?.email || relayState?.email;

                    const relayStateToPass = { email };
                    const returnTo = req?.query?.returnTo || '';
                    const redirectTo = req?.query?.redirectTo || '';

                    if (!!returnTo) {
                        relayStateToPass['returnTo'] = returnTo;
                    }
                    if (!!redirectTo) {
                        relayStateToPass['redirectTo'] = redirectTo;
                    }

                    req.query.RelayState = encodeURIComponent(JSON.stringify(relayStateToPass));

                    const user = await this.databaseService.getUserFromDB({
                        userIdEmailPhone: email,
                    });
                    // console.log("user", user);
                    if (!user || !user.orgID) {
                        return done(null, {
                            errorMessage: 'User or Organization not found',
                        });
                    }

                    const organization = await this.databaseService.getOrganization({
                        orgID: user.orgID,
                    });

                    const idpConfig = organization?.idpConfig;
                    // console.log('idpConfig', idpConfig);

                    if (!organization || !idpConfig?.entryPoint || !idpConfig?.cert) {
                        return done(null, {
                            errorMessage: 'Organization or SSO Configuration not found!',
                        });
                    }
                    const samlOptions: SamlConfig = {
                        issuer: Config.SAML.ISSUER,
                        entryPoint: idpConfig?.entryPoint,
                        cert: idpConfig?.cert,
                        callbackUrl: Config.SAML.CALLBACK_URL,
                        idpIssuer: idpConfig?.issuer,
                        logoutCallbackUrl: Config.SAML.LOGOUT_CALLBACK_URL,
                        logoutUrl: idpConfig?.logoutUrl,
                        forceAuthn: true,
                    };

                    return done(null, samlOptions);
                } catch (error) {
                    console.error('SAML Config Error:', error);
                    return done(error);
                }
            },
        });
    }

    // This method will be called after successful authentication
    async validate(req: Request, profile, done) {
        console.log('SAML Authenticated Profile:', profile);
        // console.log('done', done);
        let returnTo = '';
        let redirectTo = '';
        const relayState = req?.body?.RelayState
            ? JSON.parse(decodeURIComponent(req?.body?.RelayState))
            : {};

        if (relayState?.returnTo) returnTo = relayState.returnTo;
        if (relayState?.redirectTo) redirectTo = relayState.redirectTo;


        if (relayState?.email !== profile?.email) {
            return done(null, {
                errorMessage:
                    'Something went wrong, Please logout from your IDP and try again!',
            });
        }

        req.query.returnTo = returnTo;
        req.query.redirectTo = redirectTo;
        return done(null, profile);
    }
}

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