Skip to content

feat: give a customer access token an hour instead of five minutes - #273

Merged
kcrmin merged 1 commit into
developfrom
feature/273-access-token-lifetime
Aug 9, 2026
Merged

feat: give a customer access token an hour instead of five minutes#273
kcrmin merged 1 commit into
developfrom
feature/273-access-token-lifetime

Conversation

@kcrmin

@kcrmin kcrmin commented Aug 9, 2026

Copy link
Copy Markdown
Member

identity.jwt.access-lifetime 기본값이 5분이었다. 실제 사용에서 버티지 못했다 — 로그인 후 전략 작성 →
검증 → 출시를 이어서 진행하면 중간에 AUTHENTICATION_REJECTED 가 났고, 하나의 연속된 작업 안에서
재인증을 해야 했다.

토큰의 실제 무효화 수단은 만료 시간이 아니라 auth epoch 과 credential version 이다. 둘 다 매 요청에서
검사되고, 이 값과 무관하게 즉시 효력이 있다 — 비밀번호 변경·로그아웃·제재는 모두 그 경로로 즉시 반영된다.
만료를 짧게 두는 것은 "탈취됐고 아직 무효화되지 않은" 토큰의 창을 좁히는 효과뿐이고, 1시간은 그에 대한
통상적인 절충이다.

무엇을 바꿨나

identity.jwt.access-lifetime    PT5M   → PT1H
identity.jwt.refresh-lifetime   PT720H → PT720H (그대로, 30일)

refresh 는 이미 30일이라 access 를 1시간으로 올려도 관계가 바뀌지 않는다. access 가 refresh 보다 길어지면
refresh 자체가 무의미해지므로 그 순서를 테스트로 고정했다.

두 기본값을 @Value 안 리터럴에서 이름 있는 상수로 뺐다. 이게 이 PR 에서 진짜 중요한 부분이다 —
기존에는 배선된 기본값을 어떤 테스트도 볼 수 없었다. 코덱 테스트는 전부 자기 수명을 직접 넘겨 코덱을
만들기 때문에, 배포된 기본값이 5분이든 얼마든 모든 테스트가 그대로 통과한다. 5분이 배포 환경까지 간 경로가
그것이다. refresh-lifetime 리터럴은 주입 지점 3곳에 복사돼 있던 것도 하나로 합쳤다.

테스트

IdentityTokenLifetimeDefaultsTest:

  • DEFAULT_ACCESS_LIFETIME = 1시간, DEFAULT_REFRESH_LIFETIME = 30일, 그리고 access < refresh
  • 문자열 확인에 그치지 않고 기본값을 끝까지 읽어서 동작으로 확인: t 에 발급한 토큰이 t+59m 에 검증되고
    (5분으로는 불가능했던 바로 그것), t+61m 에는 거부된다
./gradlew :apps:backend-api:test --tests '*IdentityTokenLifetimeDefaultsTest*' --tests '*CustomerJwtCodecTest*'
IdentityTokenLifetimeDefaultsTest  2 tests, 0 failures
CustomerJwtCodecTest               2 tests, 0 failures

범위 밖

운영자 평면의 OPERATOR_AUTH_MAXIMUM_TOKEN_AGE 도 PT5M 이지만 건드리지 않았다. 그건 고객 세션 수명이
아니라 Cognito ID 토큰의 허용 최대 나이이고, 관리 평면의 보안 태세 판단이라 별도 결정이 필요하다.

권한 요청자: user:kcrmin — "ACCESS TOKEN이 5분은 말도 안되게 적어 / 1시간으로 해주고 REFRESH TOKEN 도
그에 맞게 늘려" (specs/·contracts/ 는 이 값을 고정하지 않으므로 canonical-write 게이트 대상은 아니다).

루트 포인터와 배포는 이 PR 에 포함되지 않는다.

Five minutes did not survive real use. A session that logged in and then worked
through authoring, validation and release was rejected mid-sequence, so one
continuous task required re-authenticating inside itself.

The token's real revocation levers are the auth epoch and the credential
version, both checked on every request and both effective immediately whatever
this value is. A short expiry only narrows the window for a stolen token that is
not otherwise revoked, and an hour is the ordinary trade for that.

The refresh lifetime is unchanged at 30 days: it was already far longer than an
access token and nothing about this change moves it.

Both defaults are now named constants instead of literals repeated inside
@value annotations, because no test could see the wired values — every codec
test constructs its own lifetime, so all of them kept passing while the deployed
default was five minutes. The new test asserts the constants and reads the
access default end to end: a token issued at t verifies at t+59m and is rejected
at t+61m.
@kcrmin
kcrmin merged commit f829715 into develop Aug 9, 2026
2 checks passed
@kcrmin
kcrmin deleted the feature/273-access-token-lifetime branch August 9, 2026 15:41
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.

1 participant