Switch VK ID authentication to use JWT tokens

- Replace access token with JWT id token
- Add JWT public key for VK ID
- Implement JWT verification and decoding
- Validate JWT issuer and app ID
This commit is contained in:
2025-01-26 19:20:16 +04:00
parent 75e5874dc2
commit bb73b83edf
3 changed files with 44 additions and 23 deletions

View File

@@ -9,13 +9,14 @@ import { compare, genSalt, hash } from "bcrypt";
import UserRole from "../users/user-role.enum"; import UserRole from "../users/user-role.enum";
import User from "../users/entity/user.entity"; import User from "../users/entity/user.entity";
import ChangePasswordDto from "./dto/change-password.dto"; import ChangePasswordDto from "./dto/change-password.dto";
import axios from "axios";
import SignInErrorDto, { SignInErrorCode } from "./dto/sign-in-error.dto"; import SignInErrorDto, { SignInErrorCode } from "./dto/sign-in-error.dto";
import { SignUpDto, SignUpVKDto } from "./dto/sign-up.dto"; import { SignUpDto, SignUpVKDto } from "./dto/sign-up.dto";
import SignUpErrorDto, { SignUpErrorCode } from "./dto/sign-up-error.dto"; import SignUpErrorDto, { SignUpErrorCode } from "./dto/sign-up-error.dto";
import { SignInDto, SignInVKDto } from "./dto/sign-in.dto"; import { SignInDto, SignInVKDto } from "./dto/sign-in.dto";
import ObjectID from "bson-objectid"; import ObjectID from "bson-objectid";
import UserDto from "../users/dto/user.dto"; import UserDto from "../users/dto/user.dto";
import { decodeJwt, verifyJwtSignature } from "firebase-admin/lib/utils/jwt";
import { vkIdConstants } from "../contants";
@Injectable() @Injectable()
export class AuthService { export class AuthService {
@@ -123,7 +124,7 @@ export class AuthService {
/** /**
* Парсит VK ID пользователя по access token * Парсит VK ID пользователя по access token
* *
* @param accessToken - Access token пользователя VK * @param idToken - Access token пользователя VK
* @returns Promise, который разрешается в VK ID пользователя или null в случае ошибки * @returns Promise, который разрешается в VK ID пользователя или null в случае ошибки
* *
* @example * @example
@@ -134,26 +135,31 @@ export class AuthService {
* console.error('Ошибка при получении VK ID'); * console.error('Ошибка при получении VK ID');
* } * }
*/ */
private static async parseVKID(accessToken: string): Promise<number> { private static async parseVKID(idToken: string): Promise<number> {
const form = new FormData(); try {
form.append("access_token", accessToken); await verifyJwtSignature(idToken, vkIdConstants.jwtPubKey, {
form.append("v", "5.199"); issuer: "VK",
jwtid: "21",
const response = await axios.post( });
"https://api.vk.com/method/account.getProfileInfo", } catch {
form,
{ responseType: "json" },
);
const data: { error?: any; response?: { id: number } } =
response.data as object;
if (response.status !== 200 || data.error !== undefined) {
console.warn(data);
return null; return null;
} }
return data.response.id; const decodedToken = await decodeJwt(idToken);
type TokenData = {
iis: string;
sub: number;
app: number;
exp: number;
iat: number;
jti: number;
};
const payload = decodedToken.payload as TokenData;
if (payload.app !== vkIdConstants.clientId) return null;
return payload.sub;
} }
/** /**

View File

@@ -6,6 +6,21 @@ configDotenv();
export const vkIdConstants = { export const vkIdConstants = {
clientId: +process.env.VKID_CLIENT_ID, clientId: +process.env.VKID_CLIENT_ID,
redirectUri: process.env.VKID_REDIRECT_URI, redirectUri: process.env.VKID_REDIRECT_URI,
jwtPubKey:
"-----BEGIN PUBLIC KEY-----\n" +
"MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvsvJlhFX9Ju/pvCz1frB\n" +
"DgJs592VjdwQuRAmnlJAItyHkoiDIOEocPzgcUBTbDf1plDcTyO2RCkUt0pz0WK6\n" +
"6HNhpJyIfARjaWHeUlv4TpuHXAJJsBKklkU2gf1cjID+40sWWYjtq5dAkXnSJUVA\n" +
"UR+sq0lJ7GmTdJtAr8hzESqGEcSP15PTs7VUdHZ1nkC2XgkuR8KmKAUb388ji1Q4\n" +
"n02rJNOPQgd9r0ac4N2v/yTAFPXumO78N25bpcuWf5vcL9e8THk/U2zt7wf+aAWL\n" +
"748e0pREqNluTBJNZfmhC79Xx6GHtwqHyyduiqfPmejmiujNM/rqnA4e30Tg86Yn\n" +
"cNZ6vLJyF72Eva1wXchukH/aLispbY+EqNPxxn4zzCWaLKHG87gaCxpVv9Tm0jSD\n" +
"2es22NjrUbtb+2pAGnXbyDp2eGUqw0RrTQFZqt/VcmmSCE45FlcZMT28otrwG1ZB\n" +
"kZAb5Js3wLEch3ZfYL8sjhyNRPBmJBrAvzrd8qa3rdUjkC9sKyjGAaHu2MNmFl1Y\n" +
"JFQ3J54tGpkGgJjD7Kz3w0K6OiPDlVCNQN5sqXm24fCw85Pbi8SJiaLTp/CImrs1\n" +
"Z3nHW5q8hljA7OGmqfOP0nZS/5zW9GHPyepsI1rW6CympYLJ15WeNzePxYS5KEX9\n" +
"EncmkSD9b45ge95hJeJZteUCAwEAAQ==\n" +
"-----END PUBLIC KEY-----",
}; };
export const jwtConstants = { export const jwtConstants = {

View File

@@ -21,16 +21,16 @@ export class VKIDService {
`redirect_uri=${vkIdConstants.redirectUri}`, `redirect_uri=${vkIdConstants.redirectUri}`,
); );
const accessToken: string = (response.data as { access_token: string }) const idToken: string = (response.data as { id_token: string })
.access_token; .id_token;
if (!accessToken) { if (!idToken) {
console.error(response.data); console.error(response.data);
return null; return null;
} }
return new OAuthResponseDto({ return new OAuthResponseDto({
accessToken: accessToken, accessToken: idToken,
}); });
} }
} }