diff --git a/modules/expo-bluesky-oauth-client/ios/ExpoBlueskyOAuthClientModule.swift b/modules/expo-bluesky-oauth-client/ios/ExpoBlueskyOAuthClientModule.swift index 510d60fdc6..ee0b4f5ce5 100644 --- a/modules/expo-bluesky-oauth-client/ios/ExpoBlueskyOAuthClientModule.swift +++ b/modules/expo-bluesky-oauth-client/ios/ExpoBlueskyOAuthClientModule.swift @@ -38,8 +38,8 @@ public class ExpoBlueskyOAuthClientModule: Module { promise.resolve(jwt) } - AsyncFunction("verifyJwt") { (jwk: String, token: String, options: String?, promise: Promise) in - promise.resolve(JWTUtil.verifyJwt(jwk, token: token, options: options)) + AsyncFunction("verifyJwt") { (token: String, jwk: JWK, promise: Promise) in + promise.resolve(JWTUtil.verifyJwt(token: token, jwk: jwk)) } } } diff --git a/modules/expo-bluesky-oauth-client/ios/JWK.swift b/modules/expo-bluesky-oauth-client/ios/JWK.swift index e9d80fe3b4..daa7206914 100644 --- a/modules/expo-bluesky-oauth-client/ios/JWK.swift +++ b/modules/expo-bluesky-oauth-client/ios/JWK.swift @@ -27,7 +27,7 @@ struct JWK : Record { return Field(wrappedValue: self) } - func toSecKey() throws -> SecKey? { + func toPrivateSecKey() throws -> SecKey? { let jsonData = try JSONSerialization.data(withJSONObject: self.toDictionary()) guard let jwk = try? JSONDecoder().decode(ECPrivateKey.self, from: jsonData), let key = try? jwk.converted(to: SecKey.self) @@ -37,6 +37,17 @@ struct JWK : Record { } return key } + + func toPublicSecKey() throws -> SecKey? { + let jsonData = try JSONSerialization.data(withJSONObject: self.toDictionary()) + guard let jwk = try? JSONDecoder().decode(ECPublicKey.self, from: jsonData), + let key = try? jwk.converted(to: SecKey.self) + else { + print("Error creating SecKey.") + return nil + } + return key + } } struct JWKPair : Record { diff --git a/modules/expo-bluesky-oauth-client/ios/JWT.swift b/modules/expo-bluesky-oauth-client/ios/JWT.swift index b6c27a8c66..dc3dbf9f54 100644 --- a/modules/expo-bluesky-oauth-client/ios/JWT.swift +++ b/modules/expo-bluesky-oauth-client/ios/JWT.swift @@ -7,14 +7,10 @@ struct JWTHeader : Record { @Field var jku: String? @Field + var jwk: JWK + @Field var kid: String? @Field - var x5u: String? - @Field - var x5c: String? - @Field - var x5t: String? - @Field var typ: String? @Field var cty: String? @@ -179,3 +175,10 @@ struct JWTPayloadAuthorizationDetails : Record { return Field(wrappedValue: self) } } + +struct JWTVerifyResponse : Record { + @Field + var protectedHeader: JWTHeader + @Field + var payload: String +} diff --git a/modules/expo-bluesky-oauth-client/ios/JWTUtil.swift b/modules/expo-bluesky-oauth-client/ios/JWTUtil.swift index fc10136c86..f14d2a113d 100644 --- a/modules/expo-bluesky-oauth-client/ios/JWTUtil.swift +++ b/modules/expo-bluesky-oauth-client/ios/JWTUtil.swift @@ -1,3 +1,4 @@ +import ExpoModulesCore import JOSESwift class JWTUtil { @@ -34,7 +35,7 @@ class JWTUtil { public static func createJwt(header: JWTHeader, payload: JWTPayload, jwk: JWK) -> String? { guard let header = try? header.toJWSHeader(), let payload = try? payload.toPayload(), - let key = try? jwk.toSecKey() + let key = try? jwk.toPrivateSecKey() else { print("didn't have one") return nil @@ -52,15 +53,34 @@ class JWTUtil { return jws.compactSerializedString } - public static func verifyJwt(_ jwkString: String, token tokenString: String, options optionsString: String?) -> Bool { - guard let key = try? jsonToPublicKey(jwkString), - let jws = try? JWS(compactSerialization: tokenString), + public static func verifyJwt(token: String, jwk: JWK) -> JWTVerifyResponse? { + guard let key = try? jwk.toPublicSecKey(), + let jws = try? JWS(compactSerialization: token), let verifier = Verifier(verifyingAlgorithm: .ES256, key: key), - let isVerified = try? jws.validate(using: verifier).isValid(for: verifier) + let validation = try? jws.validate(using: verifier) else { - return false + return nil } - return isVerified + let header = validation.header + let serializedHeader = JWTHeader( + alg: "ES256", + jku: Field(wrappedValue: header.jku?.absoluteString), + kid: Field(wrappedValue:header.kid), + typ: Field(wrappedValue: header.typ), + cty: Field(wrappedValue: header.cty), + crit: Field(wrappedValue: header.cty) + ) + + let payload = String(data: validation.payload.data(), encoding: .utf8) + + guard let payload = payload else { + return nil + } + + return JWTVerifyResponse( + protectedHeader: serializedHeader.toField(), + payload: payload.toField() + ) } } diff --git a/modules/expo-bluesky-oauth-client/src/oauth-client-react-native.ts b/modules/expo-bluesky-oauth-client/src/oauth-client-react-native.ts index 7fd1889d44..2816b7055a 100644 --- a/modules/expo-bluesky-oauth-client/src/oauth-client-react-native.ts +++ b/modules/expo-bluesky-oauth-client/src/oauth-client-react-native.ts @@ -40,7 +40,7 @@ export const OauthClientReactNative = (NativeModule as null) || { _token: Jwt, _jwk: Jwk, ): Promise<{ - payload: Record + payload: string // this is a JSON response to make Swift a bit easier to work with protectedHeader: Record }> { throw new Error(LINKING_ERROR) diff --git a/modules/expo-bluesky-oauth-client/src/react-native-key.ts b/modules/expo-bluesky-oauth-client/src/react-native-key.ts index d4b8ef355c..5b769d348d 100644 --- a/modules/expo-bluesky-oauth-client/src/react-native-key.ts +++ b/modules/expo-bluesky-oauth-client/src/react-native-key.ts @@ -2,7 +2,6 @@ import { jwkValidator, Jwt, JwtHeader, - jwtHeaderSchema, JwtPayload, jwtPayloadSchema, Key, @@ -45,8 +44,13 @@ export class ReactNativeKey extends Key { >(token: Jwt, options?: VerifyOptions): Promise> { const result = await OauthClientReactNative.verifyJwt(token, this.jwk) - const payload = jwtPayloadSchema.parse(result.payload) - const protectedHeader = jwtHeaderSchema.parse(result.protectedHeader) + let payloadParsed = JSON.parse(result.payload) + payloadParsed = Object.fromEntries( + Object.entries(payloadParsed as object).filter(([_, v]) => v !== null), + ) + + const payload = jwtPayloadSchema.parse(payloadParsed) + const protectedHeader = result.protectedHeader if (options?.audience != null) { const audience = Array.isArray(options.audience) @@ -85,6 +89,8 @@ export class ReactNativeKey extends Key { } } + console.log(payload) + if (payload.iat == null) { throw new Error('Missing issued at') } diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index b201db08a9..f50d9a8fca 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -64,17 +64,11 @@ function HomeScreenReady({ }, { sub: 'test', + iat: Math.floor(Date.now() / 1000), }, ) - console.log(jwt) - - // console.log('public', key.publicJwk) - // const jwt = await key.createJwt( - // {alg: 'ES256', kid: key.kid}, - // {sub: 'test'}, - // ) - // console.log(jwt) + const verified = await key.verifyJwt(jwt) })() }, [])