Closed anapeksha closed 2 months ago
I am integrating oauth2orize with Nestjs to create an OAuth server which supports only Authorization code grant type.
oauth.service.ts
import { Injectable, InternalServerErrorException, NotFoundException, UnauthorizedException, } from "@nestjs/common"; import { Client, User } from "@prisma/client"; import oauth2orize, { ExchangeDoneFunction, IssueGrantCodeDoneFunction, OAuth2Server, exchange, grant, } from "oauth2orize"; import { PrismaService } from "../prisma/prisma.service"; @Injectable() export class OAuthService { private _server = oauth2orize.createServer<Client, User>(); constructor(private readonly prismaService: PrismaService) { this._server.serializeClient((client, done) => { return done(null, client.id); }); this._server.deserializeClient(async (id, done) => { try { const client = await this.prismaService.client.findUniqueOrThrow({ where: { id }, }); done(null, client); } catch (err) { if (err.code === "P2025") { done(new UnauthorizedException("Client not found")); } done(new InternalServerErrorException(err)); } }); this._server.grant( grant.code( async ( client, redirectUri, user, res, req, done: IssueGrantCodeDoneFunction ) => { try { const authorizationCode = await this.prismaService.authorizationCode.create({ data: { clientId: client.id, userId: user ? user.id : client.userId, redirectUri, scope: req.scope, state: req.state, expiresAt: new Date(Date.now() + 10 * 60 * 1000), }, }); done(null, authorizationCode.code); } catch (err) { done( new InternalServerErrorException( "Failed to create authorization code" ) ); } } ) ); this._server.exchange( exchange.code( async ( client, code, redirectUri, body, authInfo, done: ExchangeDoneFunction ) => { try { if (!client) { return done(new UnauthorizedException("Client undefined")); } const authCode = await this.prismaService.authorizationCode.findUnique({ where: { code }, include: { Client: true, User: true }, }); if (!authCode) { return done(new UnauthorizedException("Invalid Grant Code")); } if (authCode.clientId !== client.id) { return done(new UnauthorizedException("Client Id mismatch")); } if (authCode.redirectUri !== redirectUri) { return done(new UnauthorizedException("Redirect URI mismatch")); } const accessToken = await this.prismaService.accessToken.create({ data: { clientId: client.id, userId: authCode.userId, scope: authCode.scope, expiresAt: new Date(Date.now() + 3600 * 1000), }, }); await this.prismaService.authorizationCode.delete({ where: { id: authCode.id }, }); done(null, accessToken.token); } catch (err) { done(new InternalServerErrorException(err)); } } ) ); } public getServer(): OAuth2Server<Client, User> { return this._server; } public async findClientById(clientId: string) { try { return await this.prismaService.client.findUniqueOrThrow({ where: { id: clientId, }, }); } catch (err) { if (err.code === "P2025") { throw new NotFoundException("Client not found"); } throw new InternalServerErrorException(err); } } public async findAccessToken(token: string) { try { return await this.prismaService.accessToken.findUniqueOrThrow({ where: { token, }, }); } catch (err) { if (err.code === "P2025") { throw new UnauthorizedException("Token not found"); } throw new InternalServerErrorException(err); } } }
oauth.module.ts
import { MiddlewareConsumer, Module, NestModule, RequestMethod, } from "@nestjs/common"; import { Client } from "@prisma/client"; import { ValidateDoneFunction } from "oauth2orize"; import { PrismaService } from "../prisma/prisma.service"; import { OAuthController } from "./oauth.controller"; import { OAuthService } from "./oauth.service"; @Module({ controllers: [OAuthController], providers: [OAuthService, PrismaService], }) export class OAuthModule implements NestModule { constructor(private oAuthService: OAuthService) {} configure(consumer: MiddlewareConsumer) { consumer .apply( this.oAuthService .getServer() .authorization( async ( clientId, redirectUri, done: ValidateDoneFunction<Client> ) => { try { const client = await this.oAuthService.findClientById(clientId); return done(null, client, redirectUri); } catch (err) { done(err); } } ) ) .forRoutes({ path: "oauth/authorize", method: RequestMethod.GET }); consumer .apply(this.oAuthService.getServer().decision()) .forRoutes({ path: "oauth/decision", method: RequestMethod.POST }); consumer .apply( this.oAuthService.getServer().token(), this.oAuthService.getServer().errorHandler() ) .forRoutes({ path: "oauth/token", method: RequestMethod.POST, }); return; } }
authentication has been excluded to test the authorization flow.
I have logged the clients in multiple places, it exists everywhere. But, not in the exchange step.
My bad! Needed to attach the client to req.user before passing control to token().
I am integrating oauth2orize with Nestjs to create an OAuth server which supports only Authorization code grant type.
oauth.service.ts
oauth.module.ts
authentication has been excluded to test the authorization flow.
I have logged the clients in multiple places, it exists everywhere. But, not in the exchange step.