Ruby 애플리케이션에 인증 (Authentication)을 추가하세요
- 다음 데모는 Ruby 3.3.3을 기반으로 구축되었습니다.
- 샘플 프로젝트는 GitHub 저장소에서 확인할 수 있습니다.
사전 준비 사항
- Logto Cloud 계정 또는 셀프 호스팅 Logto.
- Logto 전통적인 웹 애플리케이션 생성.
설치
Logto SDK를 번들러를 통해 설치하세요:
bundle add logto
또는 선호하는 방법으로 gem을 추가하세요.
통합
다음 데모는 Ruby on Rails를 위한 것입니다. 그러나 동일한 단계를 다른 Ruby 프레임워크에도 적용할 수 있습니다.
Logto 클라이언트 초기화
Logto 클라이언트를 초기화하려는 파일 (예: 기본 컨트롤러 또는 미들웨어)에서 다음 코드를 추가하세요:
require "logto/client"
@client = LogtoClient.new(
config: LogtoClient::Config.new(
endpoint: "https://your-logto-endpoint.com",
app_id: "your-logto-app-id",
app_secret: "your-logto-app-secret"
),
navigate: ->(uri) { a_redirect_method(uri) },
storage: LogtoClient::SessionStorage.new(the_session_object)
)
end
예를 들어, Rails 컨트롤러에서는 코드는 다음과 같을 수 있습니다:
require "logto/client"
class SampleController < ApplicationController
before_action :initialize_logto_client
private
def initialize_logto_client
@client = LogtoClient.new(
config: LogtoClient::Config.new(
# ...your configuration
),
# 클라이언트가 다른 호스트 (즉, Logto 테넌트)로 리디렉션할 수 있도록 허용
navigate: ->(uri) { redirect_to(uri, allow_other_host: true) },
# 컨트롤러는 세션 객체에 접근할 수 있습니다
storage: LogtoClient::SessionStorage.new(session)
)
end
end
리디렉션 URI 구성
세부 사항을 살펴보기 전에, 최종 사용자 경험에 대한 간단한 개요를 소개합니다. 로그인 과정은 다음과 같이 간소화될 수 있습니다:
- 귀하의 앱이 로그인 메서드를 호출합니다.
- 사용자는 Logto 로그인 페이지로 리디렉션됩니다. 네이티브 앱의 경우, 시스템 브라우저가 열립니다.
- 사용자가 로그인하고 귀하의 앱으로 다시 리디렉션됩니다 (리디렉션 URI로 구성됨).
리디렉션 기반 로그인에 관하여
- 이 인증 과정은 OpenID Connect (OIDC) 프로토콜을 따르며, Logto는 사용자 로그인을 보호하기 위해 엄격한 보안 조치를 시행합니다.
- 여러 앱이 있는 경우, 동일한 아이덴티티 제공자 (Logto)를 사용할 수 있습니다. 사용자가 한 앱에 로그인하면, Logto는 사용자가 다른 앱에 접근할 때 자동으로 로그인 과정을 완료합니다.
리디렉션 기반 로그인에 대한 이론적 배경과 이점에 대해 더 알고 싶다면, Logto 로그인 경험 설명을 참조하세요.
다음 코드 스니펫에서는, 여러분의 앱이 http://localhost:3000/
에서 실행되고 있다고 가정합니다.
리디렉션 URI 구성
Logto Console의 애플리케이션 세부 정보 페이지로 이동합니다. 리디렉션 URI http://localhost:3000/callback
를 추가하세요.
로그인과 마찬가지로, 사용자는 공유 세션에서 로그아웃하기 위해 Logto로 리디렉션되어야 합니다. 완료되면 사용자를 다시 웹사이트로 리디렉션하면 좋습니다. 예를 들어, 로그아웃 후 리디렉션 URI 섹션에 http://localhost:3000/
를 추가하세요.
그런 다음 "저장"을 클릭하여 변경 사항을 저장하세요.
콜백 처리
리디렉션 URI가 http://localhost:3000/callback
으로 설정되었으므로, 애플리케이션에서 이를 처리해야 합니다. Rails 컨트롤러에서 다음 코드를 추가할 수 있습니다:
class SampleController < ApplicationController
def callback
@client.handle_sign_in_callback(url: request.original_url)
end
end
그리고 config/routes.rb
에서 경로를 구성하세요:
Rails.application.routes.draw do
get "/callback", to: "sample#callback"
end
로그인 및 로그아웃 호출
애플리케이션에서 로그인 및 로그아웃을 호출하는 다양한 방법이 있습니다. 예를 들어, Rails 애플리케이션에서 두 개의 경로를 구현할 수 있습니다:
class SampleController < ApplicationController
def sign_in
@client.sign_in(redirect_uri: request.base_url + "/callback")
end
def sign_out
@client.sign_out(post_logout_redirect_uri: request.base_url)
end
# ...
end
Rails.application.routes.draw do
get "/sign_in", to: "sample#sign_in"
get "/sign_out", to: "sample#sign_out"
# ...
end
그런 다음 뷰에서 이러한 동작을 트리거하는 버튼이나 링크를 만들 수 있습니다. 예를 들어:
<% if @client.is_authenticated? %>
<a href="<%= sign_out_path %>">로그아웃</a>
<% else %>
<a href="<%= sign_in_path %>">로그인</a>
<% end %>
체크포인트: 애플리케이션 테스트하기
이제 애플리케이션을 테스트할 수 있습니다:
- 애플리케이션을 실행하면 로그인 버튼이 표시됩니다.
- 로그인 버튼을 클릭하면 SDK가 로그인 프로세스를 초기화하고 Logto 로그인 페이지로 리디렉션됩니다.
- 로그인 후, 애플리케이션으로 다시 리디렉션되어 로그아웃 버튼이 표시됩니다.
- 로그아웃 버튼을 클릭하여 토큰 저장소를 지우고 로그아웃합니다.
사용자 정보 가져오기
사용자 정보 표시
사용자의 정보를 표시하려면 @client.id_token_claims
메서드를 사용할 수 있습니다. 예를 들어, 뷰에서:
<% if @client.is_authenticated? %>
<p>Welcome, <%= @client.id_token_claims["name"] %></p>
<% else %>
<p>Please sign in</p>
<% end %>
자세한 내용은 gemdocs의 #id_token_claims
메서드를 참조하세요.
추가 클레임 요청
id_token_claims
에서 반환된 객체에 일부 사용자 정보가 누락된 것을 발견할 수 있습니다.
이는 OAuth 2.0 및 OpenID Connect (OIDC)가 최소 권한 원칙 (PoLP)을 따르도록 설계되었기 때문이며,
Logto는 이러한 표준을 기반으로 구축되었습니다.
기본적으로 제한된 클레임 (Claim)만 반환됩니다. 더 많은 정보를 원하시면, 추가적인 스코프 (Scope)를 요청하여 더 많은 클레임에 접근할 수 있습니다.
"클레임 (Claim)"은 주체에 대해 주장하는 내용이며, "스코프 (Scope)"는 클레임의 그룹입니다. 현재의 경우, 클레임은 사용자에 대한 정보입니다.
다음은 스코프 - 클레임 관계의 비규범적 예시입니다:
"sub" 클레임은 "주체"를 의미하며, 이는 사용자의 고유 식별자 (즉, 사용자 ID)입니다.
Logto SDK는 항상 세 가지 스코프를 요청합니다: openid
, profile
, 그리고 offline_access
.
추가 스코프를 요청하려면 LogtoClient::Config
객체의 scopes
옵션을 구성할 수 있습니다:
require "logto/client"
@client = LogtoClient.new(
config: LogtoClient::Config.new(
# ...other configurations
scopes: ["email", "phone"] # 필요한 경우 더 많은 스코프 추가
),
# ...other configurations
)
그런 다음 id_token_claims
를 통해 추가 클레임에 접근할 수 있습니다:
<% if @client.is_authenticated? %>
<p>Name: <%= @client.id_token_claims["name"] %></p>
<p>Email: <%= @client.id_token_claims["email"] %></p>
<p>Phone: <%= @client.id_token_claims["phone"] %></p>
<% else %>
<p>Please sign in</p>
<% end %>
네트워크 요청이 필요한 클레임
ID 토큰의 비대화를 방지하기 위해, 일부 클레임은 네트워크 요청을 통해 가져와야 합니다. 예를 들어, custom_data
클레임은 스코프에서 요청되더라도 사용자 객체에 포함되지 않습니다. 이러한 클레임에 접근하려면, fetch_user_info
메서드를 사용할 수 있습니다:
<% if @client.is_authenticated? %>
<p>Custom data: <%= @client.fetch_user_info["custom_data"] %></p>
<!-- ... -->
스코프와 클레임
Logto는 OIDC 스코프 및 클레임 규약을 사용하여 ID 토큰 및 OIDC userinfo 엔드포인트에서 사용자 정보를 가져오기 위한 스코프와 클레임을 정의합니다. "스코프"와 "클레임"은 OAuth 2.0 및 OpenID Connect (OIDC) 사양의 용어입니다.
지원되는 스코프와 해당 클레임 (Claims) 목록은 다음과 같습니다:
openid
클레임 이름 | 유형 | 설명 | 사용자 정보 필요 여부 |
---|---|---|---|
sub | string | 사용자의 고유 식별자 | 아니요 |
profile
클레임 이름 | 유형 | 설명 | 사용자 정보 필요 여부 |
---|---|---|---|
name | string | 사용자의 전체 이름 | 아니요 |
username | string | 사용자의 사용자 이름 | 아니요 |
picture | string | 최종 사용자의 프로필 사진 URL. 이 URL은 이미지 파일 (예: PNG, JPEG, 또는 GIF 이미지 파일)을 참조해야 하며, 이미지를 포함하는 웹 페이지를 참조해서는 안 됩니다. 이 URL은 최종 사용자를 설명할 때 표시하기 적합한 프로필 사진을 구체적으로 참조해야 하며, 최종 사용자가 찍은 임의의 사진을 참조해서는 안 됩니다. | 아니요 |
created_at | number | 최종 사용자가 생성된 시간. 시간은 유닉스 에포크 (1970-01-01T00:00:00Z) 이후 밀리초 수로 표현됩니다. | 아니요 |
updated_at | number | 최종 사용자의 정보가 마지막으로 업데이트된 시간. 시간은 유닉스 에포크 (1970-01-01T00:00:00Z) 이후 밀리초 수로 표현됩니다. | 아니요 |
다른 표준 클레임인 family_name
, given_name
, middle_name
, nickname
, preferred_username
, profile
, website
, gender
, birthdate
, zoneinfo
, 및 locale
도 사용자 정보 엔드포인트를 요청할 필요 없이 profile
스코프에 포함됩니다. 위의 클레임과의 차이점은 이러한 클레임은 값이 비어 있지 않을 때만 반환되며, 위의 클레임은 값이 비어 있을 경우 null
을 반환한다는 점입니다.
표준 클레임과 달리, created_at
및 updated_at
클레임은 초 대신 밀리초를 사용합니다.
email
클레임 이름 | 유형 | 설명 | 사용자 정보 필요 여부 |
---|---|---|---|
string | 사용자의 이메일 주소 | 아니요 | |
email_verified | boolean | 이메일 주소가 검증되었는지 여부 | 아니요 |
phone
클레임 이름 | 유형 | 설명 | 사용자 정보 필요 여부 |
---|---|---|---|
phone_number | string | 사용자의 전화번호 | 아니요 |
phone_number_verified | boolean | 전화번호가 검증되었는지 여부 | 아니요 |
address
주소 클레임의 세부 사항은 OpenID Connect Core 1.0을 참조하세요.
custom_data
클레임 이름 | 유형 | 설명 | 사용자 정보 필요 여부 |
---|---|---|---|
custom_data | object | 사용자의 사용자 정의 데이터 | 예 |
identities
클레임 이름 | 유형 | 설명 | 사용자 정보 필요 여부 |
---|---|---|---|
identities | object | 사용자의 연결된 아이덴티티 | 예 |
sso_identities | array | 사용자의 연결된 SSO 아이덴티티 | 예 |
roles
클레임 이름 | 유형 | 설명 | 사용자 정보 필요 여부 |
---|---|---|---|
roles | string[] | 사용자의 역할 | 아니요 |
urn:logto:scope:organizations
클레임 이름 | 유형 | 설명 | 사용자 정보 필요 여부 |
---|---|---|---|
organizations | string[] | 사용자가 속한 조직 ID | 아니요 |
organization_data | object[] | 사용자가 속한 조직 데이터 | 예 |
urn:logto:scope:organization_roles
클레임 이름 | 유형 | 설명 | 사용자 정보 필요 여부 |
---|---|---|---|
organization_roles | string[] | 사용자가 속한 조직 역할, 형식은 <organization_id>:<role_name> | 아니요 |
성능과 데이터 크기를 고려할 때, "사용자 정보 필요 여부"가 "예"인 경우, 해당 클레임은 ID 토큰에 나타나지 않으며, userinfo 엔드포인트 응답에서 반환됩니다.
API 리소스 및 조직
먼저 🔐 역할 기반 접근 제어 (RBAC)를 읽어 Logto RBAC의 기본 개념과 API 리소스를 적절히 설정하는 방법을 이해하는 것을 권장합니다.
Logto 클라이언트 구성하기
API 리소스를 설정한 후, 애플리케이션에서 Logto를 구성할 때 이를 추가할 수 있습니다:
require "logto/client"
@client = LogtoClient.new(
config: LogtoClient::Config.new(
# ...other configurations
resources: ["https://shopping.your-app.com/api", "https://store.your-app.com/api"] # API 리소스를 추가하세요
),
# ...other configurations
)
각 API 리소스는 자체 권한 (스코프)을 가지고 있습니다.
예를 들어, https://shopping.your-app.com/api
리소스는 shopping:read
및 shopping:write
권한을 가지고 있으며, https://store.your-app.com/api
리소스는 store:read
및 store:write
권한을 가지고 있습니다.
이러한 권한을 요청하려면, 애플리케이션에서 Logto를 구성할 때 추가할 수 있습니다:
require "logto/client"
@client = LogtoClient.new(
config: LogtoClient::Config.new(
# ...other configurations
scopes: ["shopping:read", "shopping:write", "store:read", "store:write"],
resources: ["https://shopping.your-app.com/api", "https://store.your-app.com/api"]
),
# ...other configurations
)
스코프가 API 리소스와 별도로 정의된 것을 알 수 있습니다. 이는 OAuth 2.0을 위한 리소스 지표가 요청의 최종 스코프가 모든 대상 서비스의 모든 스코프의 데카르트 곱이 될 것이라고 명시하기 때문입니다.
따라서 위의 경우, Logto에서 정의된 스코프를 단순화할 수 있으며, 두 API 리소스 모두 접두사 없이 read
및 write
스코프를 가질 수 있습니다. 그런 다음, Logto 구성에서:
require "logto/client"
@client = LogtoClient.new(
config: LogtoClient::Config.new(
# ...other configurations
scopes: ["read", "write"],
resources: ["https://shopping.your-app.com/api", "https://store.your-app.com/api"]
),
# ...other configurations
)
모든 API 리소스에 대해 read
및 write
스코프를 요청하게 됩니다.
API 리소스에 정의되지 않은 스코프를 요청해도 괜찮습니다. 예를 들어, API 리소스에 email
스코프가 없더라도 email
스코프를 요청할 수 있습니다. 사용 불가능한 스코프는 안전하게 무시됩니다.
성공적으로 로그인한 후, Logto는 사용자의 역할에 따라 API 리소스에 적절한 스코프를 발급합니다.
API 리소스를 위한 액세스 토큰 가져오기
특정 API 리소스에 대한 액세스 토큰을 가져오려면 access_tpken
메서드를 사용할 수 있습니다:
token = @client.access_token(resource: "https://shopping.your-app.com/api")
이 메서드는 사용자가 관련 권한을 가지고 있을 때 API 리소스에 접근할 수 있는 JWT 액세스 토큰을 반환합니다. 현재 캐시된 액세스 토큰이 만료된 경우, 이 메서드는 자동으로 리프레시 토큰을 사용하여 새로운 액세스 토큰을 얻으려고 시도합니다.
조직 토큰 가져오기
조직이 처음이라면, 시작하기 위해 🏢 조직 (다중 테넌시)을 읽어보세요.
Logto 클라이언트를 구성할 때 LogtoCore::USER_SCOPE[:organizations]
스코프를 추가해야 합니다:
require "logto/core"
require "logto/client"
@client = LogtoClient.new(
config: LogtoClient::Config.new(
# ...other configurations
scopes: [LogtoCore::USER_SCOPE[:organizations]]
),
# ...other configurations
)
사용자가 로그인하면, 사용자에 대한 조직 토큰을 가져올 수 있습니다:
token = @client.access_token(organization_id: "organization_id")
조직 API 리소스
조직 내 API 리소스에 대한 액세스 토큰을 가져오려면, access_token
메서드를 API 리소스와 조직 ID를 매개변수로 사용하여 호출할 수 있습니다:
token = @client.access_token(
api_resource: "https://shopping.your-app.com/api",
organization_id: "organization_id"
)