#Passing a state through OIDC passport authentication

68 messages · Page 1 of 1 (latest)

tired aspen
#

Hello, I have setup an OIDC flow using @gilded olive/passport and openid-client. My end goal is to be able to redirect the user where he wanted to go in the first place when he is automatically redirected to the SSO because he is not logged.

When a user land on my frontend, he is redirect to the /login endpoint automatically. I have a guard that intercept the request and handles the redirect the authorize endpoint.

I cannot find where I can specify the state parameters that can be sent on the authorize endpoint, in order to forward what my frontend sends me in the /login request as query parameters. The goal is to retrieve the state in the /callback endpoint afterwards when the SSO redirects the user on my nest endpoint, so that I can redirect the user on its initial request afterwards.

any idea where I can specify this state ?

#

(post was too long, more info)

Login guard:

import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class LoginGuard extends AuthGuard('oidc') {
 async canActivate(context: ExecutionContext) {
   const result = (await super.canActivate(context)) as boolean;
   const request = context.switchToHttp().getRequest();
   await super.logIn(request);
   return result;
 }
}

this guard uses the OIDC strategy:


export class OidcStrategy extends PassportStrategy(Strategy, 'oidc') {
  client: Client;

  constructor(
    client: Client,
    // and otherstuff needed to generate the user properties
  ) {
    super({
      client: client,
      params: {
        scope: 'openid profile email',
        state: 'state',
      },
      passReqToCallback: false,
      usePKCE: 'S256',
    });

    this.client = client;
  }

  async validate(tokenset: TokenSet): Promise<any> {
    const userinfo: UserinfoResponse = await this.client.userinfo(tokenset);

    try {
      const user = {
        // lots of code to generate user properties
      };

      return user;
    } catch (err) {
      throw new UnauthorizedException();
    }
  }
}
#

I'm building the client with a function

export const buildOpenIdClient = async (configService: ConfigService) => {
  const issuer = configService.get('OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER');

  const redirectUri = configService.get(
    'OAUTH2_CLIENT_REGISTRATION_LOGIN_REDIRECT_URI',
  );

  const clientId = configService.get(
    'OAUTH2_CLIENT_REGISTRATION_LOGIN_CLIENT_ID',
  );

  const clientSecret = configService.get(
    'OAUTH2_CLIENT_REGISTRATION_LOGIN_CLIENT_SECRET',
  );

  const TrustIssuer = await Issuer.discover(
    `${issuer}/.well-known/openid-configuration`,
  );

  const client = new TrustIssuer.Client({
    redirect_uris: [redirectUri],
    id_token_signed_response_alg: 'HS512',
    client_id: clientId,
    client_secret: clientSecret,
  });

  return client;
};

and the client is injected through the strategy using a factory:

const OidcStrategyFactory = {
  provide: 'OidcStrategy',
  useFactory: async (
    configService: ConfigService,
  ) => {
    const client = await buildOpenIdClient(configService);
    const strategy = new OidcStrategy(
      client, 
      // otherstuff for properties 
    );
    return strategy;
  },
  inject: [ConfigService, StrapiDatabaseService],
};
upbeat fulcrum
#

What are you using for the frontend?

tired aspen
#

Nuxt 3

upbeat fulcrum
#

As an SPA?

tired aspen
#

nope SSR + hydration

#

(hosted on a different subdmain than my nest, sharing a top domain)

upbeat fulcrum
#

Still, for OIDC to work, you'd need the frontend to reroute the user to the OIDC provider. What provider are you using?

tired aspen
#

a custom one

#

(my client's one)

#

the OIDC works

upbeat fulcrum
#

Does it actually do OAuth2/ OIDC correctly?

tired aspen
#

yes

#

plugged & play on strapi, everything works like a charm

#

and also does on nest btw

#

my only concern is that if I understand correctly, when the OIDC authorize endpoitn is called, you can specify a state query parameter that you'll retrieve in your callback endpoint when the system redirects you to your nest app

#

and I cannot find where I can specify this state

#

because I'm not directly redirecting the user to the authorize endpoint, the libs does it for me

upbeat fulcrum
#

What do you mean by the libs does it?

tired aspen
#

I mean I have a LoginGuard that intercepts the /login request, and respond with a 302 on a /authorize endpoint with different parameters (like the client id and stuff)

but im not explicitely sending this 302 so I cannot just add a "state" param to it

now I supposed I'm missing a parameter somewhere that allows me to send this state

#

I'm only configuring the client and the strategy

upbeat fulcrum
#

So, you have to have two things going.

  1. The client redirect to the OIDC login page.
  2. The server calls to the OIDC provider's token endpoint to get the "session" of the user i.e. access token, session token and id token.

Are you aware of this?

tired aspen
#

yep

upbeat fulcrum
#

Ok. Then with the client frontend redirect, you get a very shortlived token to give to your backend. Right?

tired aspen
#

exact

upbeat fulcrum
#

So, when you call via the backend to the token API of the OIDC provider (with that temp token from the frontend), that is where you can offer the state param (I believe).

#

I've not used the state param myself.

tired aspen
#

the state param (per the doc) should be in the query parameters the frontend sends to OIDC server

#

but the client only goes on this url because Nest.js sends a 302 to it, so i have to find (in nest) where i can inject this state param

upbeat fulcrum
#

Well, you don't have to send a 302, do you? Or is passport doing that.

#

I'm reading up on the state param and I'm wrong. You are right.

tired aspen
#

in the frontend this is what's happening:

frontend sends a request to Nuxt server, gets a 302 to nest/login endpoint because he is not authenticated

nest then sends a 302 with the guard to the /authorize endpoint and this is what I want to hook

then my frontend logs in and is redirect to

nest/callback (this is where i want to retrieve my state)

nest handles the /callback with a redirect to the correct page based on the state

#

and yes its passport doing it with the guard

upbeat fulcrum
#

How is the request being made to Nest to the /authorize endpoint?

tired aspen
#

the /authorize endpoint is called in the browser, Nest sends a 302 to the browser with a location header to /authorize?xxx=yyy

#

and IDK where this Location is generated, this is mainly what im looking for 😄 (so I can add my state query param)

upbeat fulcrum
#

So, the /login response is sending the location header, right?

tired aspen
#

my routes looks like this


  @UseGuards(LoginGuard)
  @Get('/login')
  login() {
    // does nothing, always redirects
  }

  @UseGuards(LoginGuard)
  @Get('/callback')
  async authRedirect(@Req() req, @Res() res) {
    return res.redirect(
      // this is where I want to retrive the state
    );
  }

#

yes

upbeat fulcrum
#

What does the LoginGuard do?

tired aspen
#

all the code is above if you need to see everything but the guard is:

import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class LoginGuard extends AuthGuard('oidc') {
  async canActivate(context: ExecutionContext) {
    const result = (await super.canActivate(context)) as boolean;
    const request = context.switchToHttp().getRequest();
    await super.logIn(request);
    return result;
  }
}

upbeat fulcrum
#

Which passport library are you using?

tired aspen
#

@nestjs/passport

#

10.0.2

upbeat fulcrum
#

Um, yeah. And are you using the passport OIDC strategy library too?

tired aspen
#

no im using openid-client directly to get the strategy and pass it

#
import { PassportStrategy } from '@nestjs/passport';
import {
  Strategy,
  Client,
  UserinfoResponse,
  TokenSet,
  Issuer,
} from 'openid-client';
#
export class OidcStrategy extends PassportStrategy(Strategy, 'oidc') {
#

There are unresolved issue on the official passport openidconnect package with the PKCE S256 that I cannot work around

upbeat fulcrum
#

And you are expecting that to just work with Passport? Tsk, tsk....

tired aspen
#

so I used the client directly

#

well it does work....

#

my SSO is working perfectly fine, im just missing a feature to forward the state

upbeat fulcrum
#

Then I'm betting bottom dollar you'll need to dig into Passport and basically take its innards and reintroduce what it does into your own library/ modify it yourself to get what you need.

#

Passport is often a black-box that doesn't get more specific things done for the dev.

tired aspen
#

yeah sadly 😦

#

ok thanks for your help, I'll try to ding a bit more

upbeat fulcrum
#

You might be much better off just dealing with the process on your own. Take out Passport and see what you need to do. I got my OIDC process going without it. 🙂

tired aspen
#

amazing reactivity btw 🙂

upbeat fulcrum
#

Dumb luck on your part. That's all. 😄

tired aspen
#

alright, I'll do that in my next project because this one is too close to the end but I'll keep that in mind 🙂

#

haha ok 😄

#

have a nice day

upbeat fulcrum
#

Good luck. You too!

sour bridgeBOT
#

This post has been marked as resolved. :white_check_mark:
Please read through the conversation and resolution if you are having the same issue, and then re-open the post if you are still having trouble, providing as much extra information as possible.