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

JWTでユーザー認証する #15570

Open
1 task
syuilo opened this issue Mar 1, 2025 · 17 comments
Open
1 task

JWTでユーザー認証する #15570

syuilo opened this issue Mar 1, 2025 · 17 comments
Labels
✨Feature This adds/improves/enhances a feature

Comments

@syuilo
Copy link
Member

syuilo commented Mar 1, 2025

Summary

JWTでユーザー認証する

Purpose

Do you want to implement this feature yourself?

  • Yes, I will implement this by myself and send a pull request
@syuilo syuilo added the ✨Feature This adds/improves/enhances a feature label Mar 1, 2025
@kakkokari-gtyih
Copy link
Contributor

場合によってはユーザー情報を別途取得しないでも良くなる

多分全情報をJWTに乗せるのは悪手

@syuilo
Copy link
Member Author

syuilo commented Mar 1, 2025

全情報は載せない

@syuilo
Copy link
Member Author

syuilo commented Mar 1, 2025

ホームタイムライン取得するAPIを考えると、必要なのはユーザーIDくらいで、ユーザー名とかは明らかに不要

@kakkokari-gtyih
Copy link
Contributor

あーサーバー側でってことか

@kakkokari-gtyih
Copy link
Contributor

kakkokari-gtyih commented Mar 1, 2025

#15567 などに対応できそう

マスターのログイントークンをJWTに入れて iat を付与したものを通常のログイントークンとする → ログイン処理ごとにどこかにJWTを格納する → ログアウト時にJWTを削除する
ペイロードにユーザーIDなどの変更不可能なものを含めるかたちでJWTを発行する場合にJWTを取り消す手段がないということでJWTは悪手と言われるが、ログイントークンをJWTのペイロードに入れるようにすることで、今まで通りログイントークンの再生成を行えば今までのログイントークンを含んだJWTも無効にすることができるはず

@syuilo syuilo pinned this issue Mar 1, 2025
@syuilo
Copy link
Member Author

syuilo commented Mar 1, 2025

#15567 などに対応できそう

マスターのログイントークンをJWTに入れて iat を付与したものを通常のログイントークンとする → ログイン処理ごとにどこかにJWTを格納する → ログアウト時にJWTを削除する ペイロードにユーザーIDなどの変更不可能なものを含めるかたちでJWTを発行する場合にJWTを取り消す手段がないということでJWTは悪手と言われるが、ログイントークンをJWTのペイロードに入れるようにすることで、今まで通りログイントークンの再生成を行えば今までのログイントークンを含んだJWTも無効にすることができるはず

これだとパフォーマンス向上のメリットは消えそう?

@syuilo
Copy link
Member Author

syuilo commented Mar 1, 2025

JWTの有効期限を数分といった短い期間にする & ステートレス
にすれば今より安全性もパフォーマンスもどっちも向上できそうに思った

@syuilo syuilo unpinned this issue Mar 1, 2025
@syuilo
Copy link
Member Author

syuilo commented Mar 2, 2025

現実的には定期的にリフレッシュが必要ということを考えるとリフレッシュトークン<->ユーザーのキャッシュは持っておく必要があり、結局はパフォーマンス向上にはつながらなさそう

@syuilo
Copy link
Member Author

syuilo commented Mar 2, 2025

いやリフレッシュの頻度によっては別にキャッシュ無くても良いか

@syuilo
Copy link
Member Author

syuilo commented Mar 4, 2025

あと署名の生成・検証でどれくらいCPUを消費するのかは気になる

@KisaragiEffective
Copy link
Collaborator

HMAC-SHA512 (HS512) であれば比較的重くなさそう
確実に言えるのはAPの署名よりは軽いこと

@eternal-flame-AD
Copy link
Contributor

I can take on this issue next week if no one is already working on an implementation, I wanted this for my own instance too.

@kakkokari-gtyih
Copy link
Contributor

kakkokari-gtyih commented Mar 9, 2025

I can take on this issue next week if no one is already working on an implementation, I wanted this for my own instance too.

Schedule is not fixed yet, but we might do the overhaul of the login auth by @acid-chicken (#13865) so maybe we should coordinate who and when to actually implement

@eternal-flame-AD
Copy link
Contributor

eternal-flame-AD commented Mar 9, 2025

I think transition to JWT (or any alternative format than random string we agree on) should be done first, because many features in #13865 are contingent upon a rich token format (tokens with structured info that backend can parse).

パスワード認証を要求するすべてのエンドポイントを sudo トークンに置き換える -> this is essentially granular token policy

sudo モード -> this needs ways to identify tokens too (like a token_sudo_until table), and we have to agree on a format first before all

SSO -> many SSO services require you to store refresh tokens, so we need a rich format too

@eternal-flame-AD
Copy link
Contributor

I propose we do this, we don't have to have me or anyone dictate how it works, we can do a few stages

  • spec out how the token is formatted, authenticated, and optionally encrypt secret state
  • discuss with @acid-chicken about their opinion on frontend adoption

On second around we implement in this order again.

@eternal-flame-AD
Copy link
Contributor

Some early thoughts, everything is illustration only and only suggestive

Inspirations and References

Static Configuration

  • max-ttl: The absolute maximum TTL for a non-root user token (3 months or so is reasonable), used for preventing users from creating indefinitely long tokens for garbage collection and security issues
  • IKM (Input Key Material): Ways to generate key material for secrets.

Illustration:

jwt:
  max-ttl: 90d

runtimeSecret:
  ikm:
    - source: env # a BASE64 encoded environment variable, cleared after reading into memory
      name: MISSKEY_RUNTIME_SECRET
    - source: file # a file path
      path: /run/secrets/misskey_runtime_secret
      clear: true
    - source: http # a URL to fetch the secret from (we don't need to implement this, just illustrating the flexibility)
      url: https://vault.example/v1/secret/data/misskey-runtime-secret

At runtime, all the IKM sources are individually passed through bcrypt to generate a key, and then HKDF-SHA512 is used to derive the runtime secret this architecture keeps our options open for:

  • needing more runtime secrets in the future, for example actor key encryption
  • using different IKM sources for different secrets without the need to recompute bcrypt
  • possibly avoid holding keys resident in memory
function deriveRuntimeSecret(key: string, externalIkms: string[]) {
    return hkdf(
        "sha512",
        [metaRepository.getBase64("runtimeSecret"), ...externalIkms.map(fromBase64)].map(ikm => bcrypt(ikm)).join(),
        metaRepository.getBase64("runtimeSecretSalt"),
        `Product: Misskey\r\nHost: ${metaRepository.get("host")}\r\nX-Secret-Type: ${key}\r\n`,
    );
}

const jwtSecret = deriveRuntimeSecret("api.auth.jwt", externalIkms);

Format

Header

  • alg: HS256/HS512, there is no need for public-key here as there is no federated authentication, this is usually fast enough, if performance is a concern, HS256 is better (smaller payload and better architecture optimizations)
  • typ: JWT

Payload Claims

  • sub: User ID (AIDX, etc)
  • exp: Expiration time (Optional)
  • aud: https://misskey.example/api/ | https://misskey.example/queue/ (Defense against issues like GHSA-38w6-vx8g-67pp)
  • iss: Hostname
  • iat: Issued at
  • jti: User ID concatenated with a 32-bit counter
  • mkPolicy: Policy that is currently effective but can be elevated by a password prompt.
  • mkSudoPolicy: Policy that is applied after a password prompt. (Something like POST /api/admin/elevate, and the token is elevated for the next 15 minutes)
  • mkBoundingExp: Bounding expiration time (Optional)
  • mkRenewalTolerance: Renewal tolerance, generally the better option is to renew at half of the TTL and continue trying, not wait until token is unusable (Optional)

A renewal is allowed if:

  • claims.exp ? claims.exp < currentTime - (claims.mkRenewalTolerance ?? 0) : true, and
  • claims.iat + max-ttl < currentTime, and
  • claims.mkBoundingExp ? claims.mkBoundingExp < currentTime : true

Policy

We will continue using the existing permission system, with some extensions:

  • meta:legacy: Apply legacy policy (basically allow all). We can gradually phase this out by:
    • In 2025.4.0, we can make this lose admin privileges.
    • In 2025.8.0, we can make this token invalid.
  • meta:web: Apply policy for the web client.
  • meta:webAdmin: Apply policy for the web control panel.
  • write:admin:bullBoard: Can exchange token for a short-lived bull board access cookie.
  • deny:<permission>: Deny the specified permission, cannot be overridden by other policies.

Elevation

Elevation is done by a POST request to /api/admin/elevate with a payload similar:

POST /api/admin/elevate
Content-Type: application/json

{
  "id": "...",
  "ttl": 900
}

The token will then have its mkSudoPolicy applied for the next 15 minutes.

@eternal-flame-AD
Copy link
Contributor

We can also do double encryption (store a DEK in the database) if rekeying is necessary, depending on the complexity desired.

https://learn.microsoft.com/en-us/azure/security/fundamentals/double-encryption

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
✨Feature This adds/improves/enhances a feature
Projects
Development

No branches or pull requests

4 participants