.NET Core (MVC) 애플리케이션에 인증 (Authentication)을 추가하세요
- 다음 데모는 .NET Core 8.0을 기반으로 구축되었습니다. SDK는 .NET 6.0 이상과 호환됩니다.
- .NET Core 샘플 프로젝트는 GitHub 저장소에서 확인할 수 있습니다.
사전 준비 사항
- Logto Cloud 계정 또는 셀프 호스팅 Logto.
- Logto 전통적인 웹 애플리케이션 생성.
설치
프로젝트에 NuGet 패키지를 추가하세요:
dotnet add package Logto.AspNetCore.Authentication
통합
Logto 인증 (Authentication) 추가
Startup.cs
(또는 Program.cs
)를 열고 Logto 인증 (Authentication) 서비스를 등록하기 위해 다음 코드를 추가하세요:
using Logto.AspNetCore.Authentication;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddLogtoAuthentication(options =>
{
options.Endpoint = builder.Configuration["Logto:Endpoint"]!;
options.AppId = builder.Configuration["Logto:AppId"]!;
options.AppSecret = builder.Configuration["Logto:AppSecret"];
});
AddLogtoAuthentication
메서드는 다음 작업을 수행합니다:
- 기본 인증 (Authentication) 스킴을
LogtoDefaults.CookieScheme
으로 설정합니다. - 기본 챌린지 스킴을
LogtoDefaults.AuthenticationScheme
으로 설정합니다. - 기본 로그아웃 스킴을
LogtoDefaults.AuthenticationScheme
으로 설정합니다. - 인증 (Authentication) 스킴에 쿠키 및 OpenID Connect 인증 핸들러를 추가합니다.
로그인 및 로그아웃 흐름
진행하기 전에, .NET Core 인증 미들웨어에서 혼란스러운 두 가지 용어를 명확히 할 필요가 있습니다:
- CallbackPath: 사용자가 로그인한 후 Logto가 사용자를 다시 리디렉션할 URI (Logto의 "리디렉션 URI")
- RedirectUri: Logto 인증 미들웨어에서 필요한 작업이 완료된 후 리디렉션될 URI.
로그인 과정은 다음과 같이 설명할 수 있습니다:
마찬가지로, .NET Core에는 로그아웃 흐름을 위한 SignedOutCallbackPath 및 RedirectUri도 있습니다.
명확성을 위해, 우리는 다음과 같이 용어를 사용할 것입니다:
우리가 사용하는 용어 | .NET Core 용어 |
---|---|
Logto 리디렉션 URI | CallbackPath |
Logto 로그아웃 후 리디렉션 URI | SignedOutCallbackPath |
애플리케이션 리디렉션 URI | RedirectUri |
리디렉션 기반 로그인에 관하여
- 이 인증 과정은 OpenID Connect (OIDC) 프로토콜을 따르며, Logto는 사용자 로그인을 보호하기 위해 엄격한 보안 조치를 시행합니다.
- 여러 앱이 있는 경우, 동일한 아이덴티티 제공자 (Logto)를 사용할 수 있습니다. 사용자가 한 앱에 로그인하면, Logto는 사용자가 다른 앱에 접근할 때 자동으로 로그인 과정을 완료합니다.
리디렉션 기반 로그인에 대한 이론적 배경과 이점에 대해 더 알고 싶다면, Logto 로그인 경험 설명을 참조하세요.
리디렉션 URI 구성
다음 코드 스니펫에서는, 여러분의 앱이 http://localhost:3000/
에서 실행되고 있다고 가정합니다.
먼저, Logto 리디렉션 URI를 구성해 봅시다. 다음 URI를 Logto 애플리케이션 세부 정보 페이지의 "리디렉션 URI" 목록에 추가하세요:
http://localhost:3000/Callback
Logto 로그아웃 후 리디렉션 URI를 구성하려면, 다음 URI를 Logto 애플리케이션 세부 정보 페이지의 "로그아웃 후 리디렉션 URI" 목록에 추가하세요:
http://localhost:3000/SignedOutCallback
기본 경로 변경
Logto 리디렉션 URI는 기본 경로로 /Callback
을 가지며, Logto 로그아웃 후 리디렉션 URI는 기본 경로로 /SignedOutCallback
을 가집니다.
특별한 요구 사항이 없다면 그대로 두어도 됩니다. 변경하고 싶다면, LogtoOptions
의 CallbackPath
와 SignedOutCallbackPath
속성을 설정할 수 있습니다:
builder.Services.AddLogtoAuthentication(options =>
{
// 다른 설정...
options.CallbackPath = "/Foo";
options.SignedOutCallbackPath = "/Bar";
});
Logto 애플리케이션 세부 정보 페이지에서 해당 값을 업데이트하는 것을 잊지 마세요.
로그인 및 로그아웃 버튼 구현
먼저, Controller
에 액션 메서드를 추가하세요. 예를 들어:
public class HomeController : Controller
{
public IActionResult SignIn()
{
// 사용자를 Logto 로그인 페이지로 리디렉션합니다.
return Challenge(new AuthenticationProperties { RedirectUri = "/" });
}
// `ControllerBase.SignOut` 메서드와의 충돌을 피하기 위해 `new` 키워드를 사용하세요.
new public IActionResult SignOut()
{
// 인증 쿠키를 지우고 사용자를 Logto 로그아웃 페이지로 리디렉션하여 Logto 세션도 지웁니다.
return SignOut(new AuthenticationProperties { RedirectUri = "/" });
}
}
그런 다음, View에 링크를 추가하세요:
<p>인증됨: @User.Identity?.IsAuthenticated</p>
@if (User.Identity?.IsAuthenticated == true) {
<a asp-controller="Home" asp-action="SignOut">로그아웃</a>
} else {
<a asp-controller="Home" asp-action="SignIn">로그인</a>
}
사용자가 인증되지 않은 경우 "로그인" 링크를 표시하고, 인증된 경우 "로그아웃" 링크를 표시합니다.
체크포인트: 애플리케이션 테스트하기
이제 애플리케이션을 테스트할 수 있습니다:
- 애플리케이션을 실행하면 로그인 버튼이 표시됩니다.
- 로그인 버튼을 클릭하면 SDK가 로그인 프로세스를 초기화하고 Logto 로그인 페이지로 리디렉션됩니다.
- 로그인 후, 애플리케이션으로 다시 리디렉션되어 로그아웃 버튼이 표시됩니다.
- 로그아웃 버튼을 클릭하여 토큰 저장소를 지우고 로그아웃합니다.
사용자 정보 가져오기
사용자 정보 표시
사용자가 인증되었는지 확인하려면 User.Identity?.IsAuthenticated
속성을 확인할 수 있습니다.
사용자 프로필 클레임을 얻으려면 User.Claims
속성을 사용할 수 있습니다:
var claims = User.Claims;
// 사용자 ID 가져오기
var userId = claims.FirstOrDefault(c => c.Type == LogtoParameters.Claims.Subject)?.Value;
클레임 이름과 그 의미에 대한 목록은 LogtoParameters.Claims
를 참조하세요.
추가 클레임 요청
User.Claims
에서 반환된 객체에 일부 사용자 정보가 누락된 것을 발견할 수 있습니다.
이는 OAuth 2.0 및 OpenID Connect (OIDC)가 최소 권한 원칙 (PoLP)을 따르도록 설계되었기 때문이며,
Logto는 이러한 표준을 기반으로 구축되었습니다.
기본적으로 제한된 클레임 (Claim)만 반환됩니다. 더 많은 정보를 원하시면, 추가적인 스코프 (Scope)를 요청하여 더 많은 클레임에 접근할 수 있습니다.
"클레임 (Claim)"은 주체에 대해 주장하는 내용이며, "스코프 (Scope)"는 클레임의 그룹입니다. 현재의 경우, 클레임은 사용자에 대한 정보입니다.
다음은 스코프 - 클레임 관계의 비규범적 예시입니다:
"sub" 클레임은 "주체"를 의미하며, 이는 사용자의 고유 식별자 (즉, 사용자 ID)입니다.
Logto SDK는 항상 세 가지 스코프를 요청합니다: openid
, profile
, 그리고 offline_access
.
추가 스코프를 요청하려면 options
객체에서 Scopes
속성을 구성할 수 있습니다:
builder.Services.AddLogtoAuthentication(options =>
{
// ...
options.Scopes = new string[] {
LogtoParameters.Scopes.Email,
LogtoParameters.Scopes.Phone
}
});
그런 다음 User.Claims
를 통해 추가 클레임에 접근할 수 있습니다:
var claims = User.Claims;
// 사용자 이메일 가져오기
var email = claims.FirstOrDefault(c => c.Type == LogtoParameters.Claims.Email)?.Value;
네트워크 요청이 필요한 클레임
사용자 객체의 비대화를 방지하기 위해 일부 클레임은 가져오기 위해 네트워크 요청이 필요합니다. 예를 들어, custom_data 클레임은 스코프에서 요청되더라도 사용자 객체에 포함되지 않습니다. 이러한 클레임을 가져오려면 options
객체에서 GetClaimsFromUserInfoEndpoint
를 true
로 설정할 수 있습니다:
builder.Services.AddLogtoAuthentication(options =>
{
// ...
options.GetClaimsFromUserInfoEndpoint = true;
});
스코프와 클레임
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 리소스를 적절히 설정하는 방법을 이해하는 것을 권장합니다.
애플리케이션에서 API 리소스 구성
API 리소스를 설정한 후, 애플리케이션에서 Logto를 구성할 때 이를 추가할 수 있습니다:
builder.Services.AddLogtoAuthentication(options =>
{
// ...
options.Resource = "https://<your-api-resource-indicator>";
});
각 API 리소스는 자체 권한 (스코프)을 가지고 있습니다.
예를 들어, https://shopping.your-app.com/api
리소스는 shopping:read
및 shopping:write
권한을 가지고 있으며, https://store.your-app.com/api
리소스는 store:read
및 store:write
권한을 가지고 있습니다.
이러한 권한을 요청하려면, 애플리케이션에서 Logto를 구성할 때 추가할 수 있습니다:
builder.Services.AddLogtoAuthentication(options =>
{
// ...
options.Resource = "https://shopping.your-app.com/api";
options.Scopes = new string[] {
"openid",
"profile",
"offline_access",
"read",
"write"
};
});
스코프가 API 리소스와 별도로 정의된 것을 알 수 있습니다. 이는 OAuth 2.0을 위한 리소스 지표가 요청의 최종 스코프가 모든 대상 서비스의 모든 스코프의 데카르트 곱이 될 것이라고 명시하기 때문입니다.
API 리소스에 정의되지 않은 스코프를 요청해도 괜찮습니다. 예를 들어, API 리소스에 email
스코프가 없더라도 email
스코프를 요청할 수 있습니다. 사용 불가능한 스코프는 안전하게 무시됩니다.
성공적으로 로그인한 후, Logto는 사용자의 역할에 따라 API 리소스에 적절한 스코프를 발급합니다.
토큰 가져오기
때때로 API 호출을 위해 액세스 토큰 또는 ID 토큰을 가져와야 할 수 있습니다. GetTokenAsync
메서드를 사용하여 토큰을 가져올 수 있습니다:
var accessToken = await HttpContext.GetTokenAsync(LogtoParameters.Tokens.AccessToken);
var idToken = await HttpContext.GetTokenAsync(LogtoParameters.Tokens.IdToken);
토큰 만료에 대해 걱정할 필요가 없습니다. 인증 미들웨어가 필요할 때 자동으로 토큰을 갱신합니다.
인증 미들웨어가 자동으로 토큰을 갱신하더라도, 사용자 객체의 클레임은 기본 OpenID Connect 인증 핸들러의 제한으로 인해 업데이트되지 않습니다. 이는 우리가 자체 인증 핸들러를 작성하면 해결될 수 있습니다.
위의 액세스 토큰은 OpenID Connect의 userinfo 엔드포인트를 위한 불투명 토큰이며, JWT 토큰이 아닙니다. API 리소스를 지정한 경우, API 리소스를 위한 액세스 토큰을 가져오기 위해 LogtoParameters.Tokens.AccessTokenForResource
를 사용해야 합니다:
var accessToken = await HttpContext.GetTokenAsync(LogtoParameters.Tokens.AccessTokenForResource);
이 토큰은 API 리소스를 대상으로 하는 JWT 토큰이 될 것입니다.