Merge pull request #3409 from bleathem/oidc-provider

Fixed the OIDC token refresh
This commit is contained in:
Ben Lambert
2020-11-25 14:07:32 +01:00
committed by GitHub
2 changed files with 52 additions and 47 deletions
@@ -54,9 +54,11 @@ describe('OidcAuthProvider', () => {
.get('/.well-known/openid-configuration')
.reply(200, issuerMetadata);
const provider = new OidcAuthProvider(clientMetadata);
const strategy = ((await provider._strategy) as any) as {
_client: ClientMetadata;
_issuer: IssuerMetadata;
const { strategy } = ((await (provider as any).implementation) as any) as {
strategy: {
_client: ClientMetadata;
_issuer: IssuerMetadata;
};
};
// Assert that the expected request to the metadaurl was made.
expect(scope.isDone()).toBeTruthy();
@@ -89,27 +91,38 @@ describe('OidcAuthProvider', () => {
const provider = new OidcAuthProvider(clientMetadata);
const req = {
method: 'GET',
url: '/?code=test2',
url: 'https://oidc.test/?code=test2',
session: ({ 'oidc:oidc.test': 'test' } as any) as Session,
} as express.Request;
await provider.handler(req);
expect(scope.isDone()).toBeTruthy();
});
const options = {
globalConfig: {
appUrl: 'https://oidc.test',
baseUrl: 'https://oidc.test',
},
config: ({
keys: jest.fn(() => ['test']),
getConfig: jest.fn(() => ({ getString: () => '' })),
} as any) as Config,
} as AuthProviderFactoryOptions;
it('createOidcProvider', () => {
it('createOidcProvider', async () => {
const scope = nock('https://oidc.test')
.get('/.well-known/openid-configuration')
.reply(200, issuerMetadata);
const options = {
globalConfig: {
appUrl: 'https://oidc.test',
baseUrl: 'https://oidc.test',
},
config: ({
keys: jest.fn(() => ['test']),
getConfig: jest.fn(() => ({
getString: (key: string) => {
const conf = {
...clientMetadata,
metadataUrl: 'https://oidc.test/.well-known/openid-configuration',
} as any;
return conf[key] as string;
},
})),
} as any) as Config,
} as AuthProviderFactoryOptions;
const provider = createOidcProvider(options) as OAuthAdapter;
console.log(provider);
expect(provider.start).toBeDefined();
await new Promise(resolve => process.nextTick(resolve)); // advance a tick to give nock a chance to intercept the request
expect(scope.isDone()).toBeTruthy();
});
});
@@ -33,10 +33,8 @@ import {
OAuthRefreshRequest,
} from '../../lib/oauth';
import {
executeFetchUserProfileStrategy,
executeFrameHandlerStrategy,
executeRedirectStrategy,
executeRefreshTokenStrategy,
PassportDoneCallback,
} from '../../lib/passport';
import { RedirectInfo, AuthProviderFactory, ProfileInfo } from '../types';
@@ -45,20 +43,25 @@ type PrivateInfo = {
refreshToken: string;
};
type OidcImpl = {
strategy: OidcStrategy<UserinfoResponse, Client>;
client: Client;
};
export type OidcAuthProviderOptions = OAuthProviderOptions & {
metadataUrl: string;
tokenSignedResponseAlg?: string;
};
export class OidcAuthProvider implements OAuthHandlers {
readonly _strategy: Promise<OidcStrategy<UserinfoResponse, Client>>;
private readonly implementation: Promise<OidcImpl>;
constructor(options: OidcAuthProviderOptions) {
this._strategy = this.setupStrategy(options);
this.implementation = this.setupStrategy(options);
}
async start(req: OAuthStartRequest): Promise<RedirectInfo> {
const strategy = await this._strategy;
const { strategy } = await this.implementation;
return await executeRedirectStrategy(req, strategy, {
accessType: 'offline',
prompt: 'none',
@@ -70,7 +73,7 @@ export class OidcAuthProvider implements OAuthHandlers {
async handler(
req: express.Request,
): Promise<{ response: OAuthResponse; refreshToken: string }> {
const strategy = await this._strategy;
const { strategy } = await this.implementation;
const { response, privateInfo } = await executeFrameHandlerStrategy<
OAuthResponse,
PrivateInfo
@@ -83,31 +86,20 @@ export class OidcAuthProvider implements OAuthHandlers {
}
async refresh(req: OAuthRefreshRequest): Promise<OAuthResponse> {
const strategy = await this._strategy;
const refreshTokenResponse = await executeRefreshTokenStrategy(
strategy,
req.refreshToken,
req.scope,
);
const {
accessToken,
params,
refreshToken: updatedRefreshToken,
} = refreshTokenResponse;
const profile = await executeFetchUserProfileStrategy(
strategy,
accessToken,
params.id_token,
);
const { client } = await this.implementation;
const tokenset = await client.refresh(req.refreshToken);
if (!tokenset.access_token) {
throw new Error('Refresh failed');
}
const profile = await client.userinfo(tokenset.access_token);
return this.populateIdentity({
providerInfo: {
accessToken,
refreshToken: updatedRefreshToken,
idToken: params.id_token,
expiresInSeconds: params.expires_in,
scope: params.scope,
accessToken: tokenset.access_token,
refreshToken: tokenset.refresh_token,
expiresInSeconds: tokenset.expires_in,
idToken: tokenset.id_token,
scope: tokenset.scope || '',
},
profile,
});
@@ -115,7 +107,7 @@ export class OidcAuthProvider implements OAuthHandlers {
private async setupStrategy(
options: OidcAuthProviderOptions,
): Promise<OidcStrategy<UserinfoResponse, Client>> {
): Promise<OidcImpl> {
const issuer = await Issuer.discover(options.metadataUrl);
const client = new issuer.Client({
client_id: options.clientId,
@@ -159,7 +151,7 @@ export class OidcAuthProvider implements OAuthHandlers {
},
);
strategy.error = console.error;
return strategy;
return { strategy, client };
}
// Use this function to grab the user profile info from the token