I am using NestJs with oidc passport strategy with identityserver as following.
import { UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, Client, UserinfoResponse, TokenSet, Issuer } from 'openid-client';
import { AuthService } from './auth.service';
export const buildOpenIdClient = async () => {
const TrustIssuer = await Issuer.discover(`http://localhost:5001/.well-known/openid-configuration`);
const client = new TrustIssuer.Client({
client_id: 'mvc',
//client_secret: 'K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols=',
client_secret: 'secret',
redirect_uris: ['http://localhost:3100/api/callback'],
post_logout_redirect_uris: ['http://localhost:3100/signout-callback-oidc'],
token_endpoint_auth_method: 'client_secret_post',
});
return client;
};
export class OidcStrategy extends PassportStrategy(Strategy, 'oidc') {
client: Client;
constructor(private readonly authService: AuthService, client: Client) {
super({
client: client,
params: {
redirect_uri: 'http://localhost:3100/api/callback',
scope: 'openid profile api1',
},
passReqToCallback: true,
usePKCE: false,
});
this.client = client;
}
async validate(tokenset: TokenSet): Promise<any> {
const userinfo: UserinfoResponse = await this.client.userinfo(tokenset); // HERE IT THROWS ERROR
try {
const id_token = tokenset.id_token
const access_token = tokenset.access_token
const refresh_token = tokenset.refresh_token
const user = {
id_token,
access_token,
refresh_token,
userinfo,
}
return user;
} catch (err) {
throw new UnauthorizedException();
}
}
}
My auth module looks like below.
// src/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { OidcStrategy, buildOpenIdClient } from './oidc.strategy';
import { SessionSerializer } from './session.serializer';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
const OidcStrategyFactory = {
provide: 'OidcStrategy',
useFactory: async (authService: AuthService) => {
const client = await buildOpenIdClient(); // secret sauce! build the dynamic client before injecting it into the strategy for use in the constructor super call.
const strategy = new OidcStrategy(authService, client);
return strategy;
},
inject: [AuthService]
};
@Module({
imports: [
PassportModule.register({ session: true, defaultStrategy: 'oidc' }),
],
controllers: [AuthController],
providers: [OidcStrategyFactory, SessionSerializer, AuthService],
})
export class AuthModule {}
So it redirects to identityserver and also gets callback with token but on this.client.userinfo(tokenset)
gives following errors.
On Identityserver it logs following 2 errors.
1) reference_token grant with value: [object Object] not found in store.
2) Invalid reference token.
{"ClientId": null, "ClientName": null, "ValidateLifetime": true, "AccessTokenType": "Reference", "ExpectedScope": "openid", "TokenHandle": "[object Object]", "JwtId": null, "Claims": null, "$type": "TokenValidationLog"}
And on NestJs it logs following.
OPError {error: 'invalid_token', message: 'invalid_token', name: 'OPError', stack: 'OPError: invalid_token
Following was the article I used as reference. https://sdoxsee.github.io/blog/2020/02/05/cats-nest-nestjs-mongo-oidc.html
Following is my identityserver client.
new Client
{
ClientId = "mvc",
ClientSecrets = { new Secret("secret") },
RequirePkce = false,
AccessTokenType = AccessTokenType.Reference,
RequireClientSecret = false,
AllowedGrantTypes = GrantTypes.Code,
// where to redirect to after login
RedirectUris = { "http://localhost:3100/api/callback" },
// where to redirect to after logout
PostLogoutRedirectUris = { "http://localhost:3100/signout-callback-oidc" },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
}
}
Ok, I was able to debug open source code and figured that article which I had referred was using different validate method definition.
It seems the definition of validate method has changed.
So tokenset I was retrieving as second parameter instead of first (in article it was first parameter).
So instead of following.
async validate(tokenset: TokenSet): Promise<any> {
I had to use following.
async validate(incomingMessage: IncomingMessage, tokenset: TokenSet): Promise<any> {
Seems Typescript doesn't help in such case...