Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: jwt signing #1967

Closed
wants to merge 2 commits into from
Closed

feat: jwt signing #1967

wants to merge 2 commits into from

Conversation

thepiwo
Copy link
Collaborator

@thepiwo thepiwo commented Mar 21, 2024

Currently based on the latest 13.2.2 release, thus the conflicts to try it easily with the current superhero wallet implementation.

related superhero wallet integration superhero-com/superhero-wallet#2875

@thepiwo thepiwo requested a review from davidyuk March 21, 2024 18:45
@thepiwo thepiwo self-assigned this Mar 21, 2024
@thepiwo thepiwo marked this pull request as draft March 21, 2024 18:45
@@ -18,6 +18,7 @@
"devDependencies": {
"@vue/cli-plugin-babel": "~5.0.8",
"@vue/cli-service": "~5.0.8",
"postcss": "^8.4.37",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems to be needed to start the example

@thepiwo thepiwo marked this pull request as ready for review March 22, 2024 15:48
@thepiwo
Copy link
Collaborator Author

thepiwo commented Mar 25, 2024

@davidyuk any feedback from your side?

@davidyuk
Copy link
Member

The motivation is unclear. Have you considered using typed data instead of JWT? Or it is not suitable for your use case?

@thepiwo
Copy link
Collaborator Author

thepiwo commented Mar 26, 2024

JWT is nice as its a widely used standard and supported by our curve, so we could expose it (and e.g. use it in superhero product login)

Copy link
Member

@davidyuk davidyuk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about motivation, seems that JWTs signed by a centralized provider are more useful/compatible that self-signed JWTs.

message: object,
options?: {
aeppOrigin?: string;
expireAt?: number; // unixtime ms
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would accept it as message.exp

): Promise<string> {
const header = { alg: 'EdDSA', typ: 'JWT' };
const payload = { ...message, exp: expireAt };
const body = `${base64url.encode(JSON.stringify(header))}.${base64url.encode(JSON.stringify(payload))}`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buffer.toString('base64url') should work instead, it is available since node@16 nodejs/node@f8ab632

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, you can use canonicalize (sdk already uses it in typed-data) instead JSON.stringify to avoid fluctuations of JWT because of different key order

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, didn't know

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buffer.toString('base64url') doesn't seem to work once built in the browser

@@ -142,6 +143,18 @@ export function sign(data: string | Uint8Array, privateKey: string | Uint8Array)
return nacl.sign.detached(Buffer.from(data), Buffer.from(privateKey));
}

export async function signJWT(
message: object,
expireAt: number,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks to be an extra argument 🤷‍♀️

message: object,
expireAt: number,
privateKey: Uint8Array,
): Promise<string> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it also can return

Promise<`${string}.${string}.${string}`>

override async signMessageJWT(message: object, options?: {
expireAt?: number;
}): Promise<string> {
const { signature } = await this._rpcClient
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

technically it is a payload + signature

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renamed it

@@ -54,6 +54,15 @@ export default abstract class AccountBase {
},
): Promise<Uint8Array>;

abstract signMessageJWT(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
abstract signMessageJWT(
abstract signJWT(

"Message" looks not necessary, because T already is "Token"

const secretKey = secretKeys.get(this);
if (secretKey == null) throw new UnexpectedTsError();
const expireAt = options?.expireAt === undefined
? new Date().getTime() + 30 * 60 * 1000 // default to 30min
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
? new Date().getTime() + 30 * 60 * 1000 // default to 30min
? Date.now() + 30 * 60 * 1000 // default to 30min

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you think its ok to keep the default expiry there?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, there are different account types and I'm not sure each of them should define it. But here is not much logic to extract to "generate jwt" function. We can do nothing here and allow the wallet to handle jwt parameters (like not allowing signing a too general token).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed it :)

expireAt: number,
privateKey: Uint8Array,
): Promise<string> {
const header = { alg: 'EdDSA', typ: 'JWT' };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EdDSA is a correct value

const header = { alg: 'EdDSA', typ: 'JWT' };
const payload = { ...message, exp: expireAt };
const body = `${base64url.encode(JSON.stringify(header))}.${base64url.encode(JSON.stringify(payload))}`;
const signature = sign(body, privateKey);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Insignificant concern is that all payloads we signed before have a special not overlapping prefix.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we add sub_jwk in the payload the risk should be even smaller

privateKey: Uint8Array,
): Promise<string> {
const header = { alg: 'EdDSA', typ: 'JWT' };
const payload = { ...message, exp: expireAt };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to store signer address somehow in message or header. It can be

sub_jwk Public key used to check the signature of an ID Token

https://www.iana.org/assignments/jwt/jwt.xhtml

Or something own like ethereum/EIPs#1341 (Issuer field?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree!

@davidyuk davidyuk mentioned this pull request Apr 5, 2024
@thepiwo thepiwo closed this Apr 29, 2024
@davidyuk davidyuk deleted the feat/jwt-signature branch June 17, 2024 02:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants