Rails 8
Rails 8 + omniauth 스타일을 쓰지 않고 직접 OAuth 클라이언트를 구현합니다 (의존성 최소화).
시작 전 체크
프로덕션 배포 시 LOGI_API_URL / LOGI_CLIENT_ID / LOGI_CLIENT_SECRET 3개를 호스팅 플랫폼 env에 모두 주입해야 합니다. 누락되면 코드는 localhost:3000으로 fallback해서 prod에서 연결 실패합니다 — 가장 흔한 실패 케이스입니다. 상세: Troubleshooting
ruby
# app/controllers/sessions_controller.rb
class LogiSessionsController < ApplicationController
def start
verifier = SecureRandom.urlsafe_base64(32).delete("=")
challenge = Base64.urlsafe_encode64(Digest::SHA256.digest(verifier), padding: false)
state = SecureRandom.hex(16)
session[:logi_pkce] = verifier
session[:logi_state] = state
redirect_to "#{ENV['LOGI_API_URL']}/oauth/authorize?" + {
client_id: ENV["LOGI_CLIENT_ID"],
redirect_uri: logi_callback_url,
response_type: "code",
scope: "profile email",
state: state,
code_challenge: challenge,
code_challenge_method: "S256",
}.to_query, allow_other_host: true
end
def callback
return render_error("state mismatch") if params[:state] != session[:logi_state]
res = Net::HTTP.post_form(
URI("#{ENV['LOGI_API_URL']}/oauth/token"),
grant_type: "authorization_code",
code: params[:code],
redirect_uri: logi_callback_url,
code_verifier: session[:logi_pkce],
client_id: ENV["LOGI_CLIENT_ID"],
client_secret: ENV["LOGI_CLIENT_SECRET"]
)
tokens = JSON.parse(res.body)
session.delete(:logi_pkce); session.delete(:logi_state)
cookies.signed.permanent[:logi_rt] = {
value: tokens["refresh_token"], httponly: true, secure: Rails.env.production?, same_site: :strict
}
redirect_to root_path
end
endJWT 검증:
ruby
gem "jwt"
jwks = JSON.parse(Net::HTTP.get(URI("#{ENV['LOGI_API_URL']}/.well-known/jwks.json")))
payload, = JWT.decode(
access_token, nil, true,
algorithms: ["RS256"],
jwks: JWT::JWK::Set.new(jwks),
iss: "logi", verify_iss: true,
aud: ENV["LOGI_CLIENT_ID"], verify_aud: true
)Scope 환경변수화 (권장)
위 예시는 scope: "profile email" 가 하드코딩돼 있습니다. 실제 운영에서는 RP 별로 요구 scope 가 다르므로 env 로 분리하세요:
ruby
scope: ENV.fetch("LOGI_SCOPES", "openid profile email")표준 조합:
- 일반 SSO:
openid profile email - 식별번호 게이팅 포함:
openid profile:basic email - ID Token 만 사용 (서버에서 토큰 검증 안 함):
openid
Scope 는 logi 앱 등록 시 allowed_scopes 와 일치해야 합니다 — 등록되지 않은 scope 를 요청하면 invalid_scope. 자세한 매트릭스: Scope 레퍼런스.
Post-deploy 검증 (가장 자주 빠지는 단계)
배포 직후 브라우저로 로그인 흘러 들어가기 전에, CLI 한 줄로 env 누락을 잡아냅니다:
bash
curl -sIL "https://yourapp.com/auth/logi/start" | grep -iE "^(HTTP|location)"기대 출력:
HTTP/2 302
location: https://api.1pass.dev/oauth/authorize?client_id=logi_xxx&...&code_challenge=...&state=...체크 포인트:
- status
302— initiate 라우트 자체가 살아있는지 - location host =
api.1pass.dev—LOGI_API_URL누락 시localhost:3000이 박혀서 옴 (가장 흔한 사고) client_id쿼리 —LOGI_CLIENT_ID누락 시 빈 값 또는 누락scope쿼리 —LOGI_SCOPES누락 시 코드 default 사용
자동화 (CI/배포 후 smoke test):
bash
curl -sI "https://yourapp.com/auth/logi/start" | grep -i "^location:" | grep -q "api.1pass.dev" \
&& echo "✓ logi env OK" || { echo "✗ LOGI_API_URL 누락 — env 점검"; exit 1; }302 만 봐선 안 됩니다
status 302 만 체크하면 localhost:3000 으로 떨어지는 케이스를 못 잡습니다. 반드시 location 의 host 까지 확인 하세요. 실제 운영 사고 다수가 이 단계에서 발생.
기존 앱에 RP 를 추가하는 절차 + 트러블슈팅: Troubleshooting.