Go net/http 세션
패키지 github.com/keenauth/keenauth-go/bff는 Go net/http 애플리케이션에 로그인 기능을 추가합니다. 서버가 OAuth 클라이언트이며 모든 토큰을 서버에 보관합니다. 브라우저는 암호화된 HttpOnly 세션 쿠키만 받으며 액세스 토큰, 갱신 토큰, ID 토큰을 전혀 보지 않습니다. 이것은 BFF(backend-for-frontend) 패턴입니다. 이 패키지는 표준 라이브러리만 사용합니다. 아직 공개되지 않았습니다.
대신 베어러 토큰을 받는 API를 보호하려면 API 보호를 참조하십시오.
시작하기 전에
- 대시보드에서 환경에 웹 애플리케이션을 생성하십시오.
- 클라이언트 인증 방식을 선택하십시오. 클라이언트 비밀 값(
bff.ClientSecretBasic) 또는private_key_jwt(bff.PrivateKeyJWT)를 사용할 수 있습니다.private_key_jwt를 사용하는 경우 애플리케이션의 공개 키를 등록하고 개인 키는 서버에 보관하십시오. - 리디렉션 URI
https://app.acme.example/auth/callback과 로그아웃 후 리디렉션 URIhttps://app.acme.example/를 추가하십시오. - 최소 32자 이상의 쿠키 비밀 값을 생성하십시오. 예를 들어
openssl rand -base64 32를 사용할 수 있습니다. 저장소가 아닌 비밀 값 관리 도구에 보관하십시오.
설정
package main
import (
"fmt"
"net/http"
"os"
"github.com/keenauth/keenauth-go/bff"
)
func main() {
auth, err := bff.New(bff.Options{
Issuer: "https://acme.eu.keenauth.app", // 환경의 issuer를 정확히 입력하십시오
ClientID: "app_01j9zzzzzzzzzzzzzzzzzzzz",
ClientAuth: bff.ClientSecretBasic{Secret: os.Getenv("KEENAUTH_CLIENT_SECRET")},
AppBaseURL: "https://app.acme.example",
Secrets: []string{os.Getenv("KEENAUTH_COOKIE_SECRET")},
})
if err != nil {
panic(err) // 설정 오류는 시작 시점에 애플리케이션을 중단합니다
}
mux := http.NewServeMux()
mux.Handle("GET /orders", auth.RequireAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
s := bff.FromContext(r.Context()).Session
fmt.Fprintf(w, "Signed in as %s", s.User.Email)
})))
http.ListenAndServe(":8080", auth.Middleware(mux))
}
클라이언트 비밀 값 대신 private_key_jwt를 사용하려면 개인 키를 전달하십시오. 패키지는 토큰 및 PAR 요청마다 새 클라이언트 assertion을 서명합니다(iss와 sub는 클라이언트 ID, aud는 issuer, 유효 기간 2분, 매번 새 jti):
pemBytes, err := os.ReadFile("/run/secrets/keenauth-app-key.pem")
if err != nil {
panic(err)
}
key, err := assertion.ParsePrivateKeyPEM(pemBytes) // import "github.com/keenauth/keenauth-go/assertion"
if err != nil {
panic(err)
}
auth, err := bff.New(bff.Options{
Issuer: "https://acme.eu.keenauth.app",
ClientID: "app_01j9zzzzzzzzzzzzzzzzzzzz",
ClientAuth: bff.PrivateKeyJWT{Key: key, KeyID: "app-key-2026-11"},
AppBaseURL: "https://app.acme.example",
Secrets: []string{os.Getenv("KEENAUTH_COOKIE_SECRET")},
})
ES256(P-256), EdDSA(Ed25519), RS256 및 PS256(RSA, 2048비트 이상)을 지원합니다. 알고리즘은 키를 따르며, Algorithm: "PS256"을 지정하면 RSA 키에 대해 PSS를 사용합니다. Key에는 모든 crypto.Signer를 사용할 수 있으므로 KMS나 HSM에 있는 키도 사용할 수 있습니다. 다른 곳에서 서명하는 경우에는 bff.PrivateKeyJWT{Assertion: f}를 설정하십시오. 여기서 f는 호출될 때마다 새로 서명된 assertion을 반환합니다. Key와 Assertion 중 하나만 설정하고 둘 다 설정하지 마십시오.
AppBaseURL은 애플리케이션의 공개 주소입니다. 패키지는 리디렉션 URI와 모든 리디렉션을 이 값으로부터 생성하며, 요청의 Host나 X-Forwarded-* 헤더로부터 생성하지 않습니다. 일반 http는 localhost에서만 허용됩니다.
라우트
Middleware는 세 개의 라우트를 제공하고 그 밖의 모든 요청에서 세션을 읽습니다. mux.Handle("/auth/", auth)로 라우트만 따로 마운트할 수도 있습니다.
| 라우트 | 기능 |
|---|---|
GET /auth/login |
로그인을 시작합니다. 선택 쿼리 매개변수: returnTo(사용자가 도착할 위치)와 organization(조직으로 로그인). |
GET /auth/callback |
로그인을 완료하고 세션 쿠키를 설정합니다. |
GET 또는 POST /auth/logout |
KeenAuth에서 로그아웃하고 애플리케이션으로 돌아갑니다. |
로그인에는 PKCE(S256)를 사용하는 인증 코드 흐름이 사용됩니다. 환경에서 지원하는 경우 요청은 서버로 푸시됩니다(PAR). 콜백에서 패키지는 state, iss, ID 토큰(nonce 포함)을 확인합니다. 로그인이 성공할 때마다 새 세션이 생성되고 이전 세션은 폐기됩니다.
returnTo는 /orders?tab=open과 같이 애플리케이션 자체의 경로여야 합니다. //evil.example이나 전체 URL과 같은 다른 값은 무시되며 사용자는 /로 이동합니다.
핸들러 보호
auth.RequireAuth(handler)는 세션이 있는 요청만 통과시킵니다.
- 세션이 없는 페이지 요청(
GET또는HEAD)은returnTo와 함께/auth/login으로 리디렉션됩니다. /api/…에 대한 요청이나 다른 메서드의 요청은 문제 문서와 함께401을 받습니다. 모든 경로에서401로 응답하려면bff.GuardOptions{API: true}를 전달하십시오.
POST, PUT, PATCH, DELETE 등 안전하지 않은 메서드의 경우 브라우저는 요청이 애플리케이션에서 왔다고 표시해야 합니다. 즉, Sec-Fetch-Site: same-origin이거나 Origin 헤더가 애플리케이션의 origin과 같아야 합니다. 그 밖의 요청은 403을 받습니다. 이는 SameSite=Lax에 더해 사이트 간 요청 위조를 방지합니다.
세션 사용 및 API 호출
bff.FromContext(r.Context())는 다음을 반환합니다.
Session:nil이거나 로그인한 사용자입니다.Session.User에는Subject,Email,Name,OrgID,OrgRoles와 같은 형식이 지정된 ID 토큰 클레임이 있으며, 모든 클레임은Session.User.Claims에 있습니다. 세션에는 토큰이 전혀 포함되지 않습니다.AccessToken: 서버 측 호출에 사용할 수 있는 유효한 액세스 토큰이거나""입니다. 이를 브라우저로 보내지 마십시오.
액세스 토큰이 60초 이내에 만료되면 패키지는 핸들러가 실행되기 전에 이를 갱신합니다. 갱신할 때마다 갱신 토큰이 교체되고 응답에 새 세션 쿠키가 설정됩니다. 서버가 갱신 토큰을 거부하면 세션이 종료됩니다. 일시적인 서버 오류의 경우 세션은 변경되지 않습니다. 한 프로세스 안의 동시 요청은 하나의 갱신을 공유합니다.
조직 전환
auth.SwitchOrganizationPath("org_01j9…", "/dashboard")는 사용자가 해당 조직으로 다시 로그인하도록 하는 /auth/login 링크를 반환합니다. KeenAuth가 멤버십을 확인합니다. 로그인 후 Session.User.OrgID에 조직이 담깁니다.
로그아웃
양식에서 /auth/logout으로 POST 요청을 보내십시오. 패키지는 세션 쿠키를 삭제하고(저장소를 사용하는 경우 저장된 세션도 삭제), 갱신 토큰을 폐기한 뒤, 브라우저를 환경의 로그아웃 엔드포인트로 보내 애플리케이션으로 돌아오게 합니다. 브라우저가 다른 사이트에서 온 요청으로 표시한 요청은 거부됩니다. Sec-Fetch-Site를 보내지 않는 브라우저는 자체 페이지에서 보낸 POST로만 로그아웃할 수 있습니다.
세션 저장소
기본적으로 전체 세션은 쿠키에 있으며, 비밀 값에서 파생된 키를 사용해 AES-256-GCM으로 암호화되고 인증됩니다. 변경되거나 복사된 쿠키는 거부됩니다. 쿠키 이름은 __Host-keenauth_session.0이며 Secure, HttpOnly, SameSite=Lax, Path=/로 설정됩니다. http://localhost에서는 __Host- 접두사와 Secure가 제외됩니다. 큰 세션은 최대 세 개의 쿠키로 나뉩니다.
토큰을 서버에 보관하려면 Options.Store를 bff.SessionStore(Get, Set, Delete)로 설정하십시오. 예를 들어 Redis를 기반으로 할 수 있습니다. 이 경우 쿠키에는 암호화된 무작위 세션 ID만 담깁니다. 로그인할 때마다 새 ID가 생성되고 이전 레코드는 삭제됩니다. 저장소에는 토큰이 담기므로 접근을 제한하십시오. bff.MemoryStore는 개발 및 테스트 용도로만 사용하십시오.
세션은 최대 7일(SessionMaxAge) 동안 유지되며, 갱신해도 이 기간은 연장되지 않습니다. 비밀 값을 변경하려면 새 값을 먼저 나열하십시오: Secrets: []string{newSecret, oldSecret}. SessionMaxAge가 지난 후 이전 값을 제거하십시오.
현재 제한 사항
- DPoP에 바인딩된 토큰과 백채널 로그아웃은 아직 처리되지 않습니다.
- 갱신은 하나의 서버 프로세스 안에서만 공유됩니다. 여러 인스턴스를 사용하는 경우 애플리케이션 설정
refresh_reuse_interval_s(기본값 5초)를 사용하면 병렬 갱신이 성공할 수 있습니다. 이 값을0으로 설정하면 두 번째 갱신이 세션을 종료합니다.