メインコンテンツまでスキップ

あなたの .NET Core (MVC) アプリケーションに認証 (Authentication) を追加する

ヒント:
  • 次のデモンストレーションは .NET Core 8.0 を基に構築されています。SDK は .NET 6.0 以上に対応しています。
  • .NET Core のサンプルプロジェクトは GitHub リポジトリ で利用可能です。

前提条件

インストール

プロジェクトに NuGet パッケージを追加します:

dotnet add package Logto.AspNetCore.Authentication

統合

Logto 認証 (Authentication) を追加する

Startup.cs(または Program.cs)を開き、次のコードを追加して Logto 認証 (Authentication) サービスを登録します:

Program.cs
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 認証 (Authentication) ハンドラーを追加します。

サインインとサインアウトのフロー

進む前に、.NET Core 認証 (Authentication) ミドルウェアにおける混乱しやすい用語を 2 つ明確にする必要があります:

  1. CallbackPath: ユーザーがサインインした後に Logto がユーザーをリダイレクトする URI (Logto における「リダイレクト URI」)
  2. RedirectUri: Logto 認証 (Authentication) ミドルウェアで必要なアクションが実行された後にリダイレクトされる URI

サインインプロセスは次のように示されます:


同様に、.NET Core にはサインアウトフローのための SignedOutCallbackPathRedirectUri もあります。

明確にするために、これらを次のように呼びます:

使用する用語.NET Core 用語
Logto リダイレクト URICallbackPath
Logto サインアウト後リダイレクト URISignedOutCallbackPath
アプリケーションリダイレクト URIRedirectUri

リダイレクトベースのサインインについて

  1. この認証 (Authentication) プロセスは OpenID Connect (OIDC) プロトコルに従い、Logto はユーザーのサインインを保護するために厳格なセキュリティ対策を講じています。
  2. 複数のアプリがある場合、同じアイデンティティプロバイダー (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 があります。

特別な要件がない場合は、そのままにしておくことができます。変更したい場合は、LogtoOptionsCallbackPathSignedOutCallbackPath プロパティを設定できます:

Program.cs
builder.Services.AddLogtoAuthentication(options =>
{
// 他の設定...
options.CallbackPath = "/Foo";
options.SignedOutCallbackPath = "/Bar";
});

Logto アプリケーション詳細ページの値もそれに応じて更新することを忘れないでください。

サインインとサインアウトボタンを実装する

まず、Controller にアクションメソッドを追加します。例えば:

Controllers/HomeController.cs
public class HomeController : Controller
{
public IActionResult SignIn()
{
// これにより、ユーザーは Logto のサインインページにリダイレクトされます。
return Challenge(new AuthenticationProperties { RedirectUri = "/" });
}

// `ControllerBase.SignOut` メソッドとの競合を避けるために `new` キーワードを使用します
new public IActionResult SignOut()
{
// これにより、認証 (Authentication) クッキーがクリアされ、ユーザーは Logto のサインアウトページにリダイレクトされ、
// Logto セッションもクリアされます。
return SignOut(new AuthenticationProperties { RedirectUri = "/" });
}
}

次に、リンクを View に追加します:

Views/Home/Index.cshtml
<p>Is authenticated: @User.Identity?.IsAuthenticated</p>
@if (User.Identity?.IsAuthenticated == true) {
<a asp-controller="Home" asp-action="SignOut">Sign out</a>
} else {
<a asp-controller="Home" asp-action="SignIn">Sign in</a>
}

ユーザーが認証 (Authentication) されていない場合は「Sign in」リンクが表示され、認証 (Authentication) されている場合は「Sign out」リンクが表示されます。

チェックポイント: アプリケーションをテストする

これで、アプリケーションをテストできます:

  1. アプリケーションを実行すると、サインインボタンが表示されます。
  2. サインインボタンをクリックすると、SDK がサインインプロセスを初期化し、Logto のサインインページにリダイレクトされます。
  3. サインインすると、アプリケーションに戻り、サインアウトボタンが表示されます。
  4. サインアウトボタンをクリックして、トークンストレージをクリアし、サインアウトします。

ユーザー情報を取得する

ユーザー情報の表示

ユーザーが認証 (Authentication) されているかどうかを確認するには、User.Identity?.IsAuthenticated プロパティをチェックできます。

ユーザープロファイルのクレームを取得するには、User.Claims プロパティを使用します:

Controllers/HomeController.cs
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 はこれらの標準に基づいて構築されているためです。

デフォルトでは、限られたクレーム (Claims) が返されます。より多くの情報が必要な場合は、追加のスコープ (Scopes) をリクエストして、より多くのクレーム (Claims) にアクセスできます。

備考:

「クレーム (Claim)」はサブジェクトについての主張であり、「スコープ (Scope)」はクレーム (Claims) のグループです。現在のケースでは、クレーム (Claim) はユーザーに関する情報の一部です。

スコープ (Scope) とクレーム (Claim) の関係の非規範的な例を示します:

ヒント:

「sub」クレーム (Claim) は「サブジェクト (Subject)」を意味し、ユーザーの一意の識別子(つまり、ユーザー ID)です。

Logto SDK は常に 3 つのスコープ (Scopes) をリクエストします:openidprofile、および offline_access

追加のスコープをリクエストするには、options オブジェクトの Scopes プロパティを設定できます:

Program.cs
builder.Services.AddLogtoAuthentication(options =>
{
// ...
options.Scopes = new string[] {
LogtoParameters.Scopes.Email,
LogtoParameters.Scopes.Phone
}
});

その後、User.Claims を介して追加のクレームにアクセスできます:

Controllers/HomeController.cs
var claims = User.Claims;

// ユーザーのメールを取得
var email = claims.FirstOrDefault(c => c.Type == LogtoParameters.Claims.Email)?.Value;

ネットワークリクエストが必要なクレーム

ユーザーオブジェクトの肥大化を防ぐために、一部のクレームは取得するためにネットワークリクエストが必要です。たとえば、custom_data クレームはスコープでリクエストされていてもユーザーオブジェクトに含まれていません。これらのクレームを取得するには、options オブジェクトで GetClaimsFromUserInfoEndpointtrue に設定できます:

Program.cs
builder.Services.AddLogtoAuthentication(options =>
{
// ...
options.GetClaimsFromUserInfoEndpoint = true;
});

スコープとクレーム

Logto は OIDC の スコープとクレームの規約 を使用して、ID トークンおよび OIDC userinfo エンドポイント からユーザー情報を取得するためのスコープとクレームを定義します。「スコープ」と「クレーム」は、OAuth 2.0 および OpenID Connect (OIDC) 仕様からの用語です。

こちらはサポートされているスコープと対応するクレーム (Claims) の一覧です:

openid

Claim nameType説明Needs userinfo?
substringユーザーの一意の識別子No

profile

Claim nameType説明Needs userinfo?
namestringユーザーのフルネームNo
usernamestringユーザーのユーザー名No
picturestringエンドユーザーのプロフィール画像の URL。この URL は画像ファイル(例:PNG、JPEG、GIF 画像ファイル)を指す必要があります。画像を含む Web ページではありません。この URL は、エンドユーザーを説明する際に表示するのに適したプロフィール写真を参照するべきであり、エンドユーザーが撮影した任意の写真ではありません。No
created_atnumberエンドユーザーが作成された時刻。時刻は Unix エポック(1970-01-01T00:00:00Z)からのミリ秒数で表されます。No
updated_atnumberエンドユーザーの情報が最後に更新された時刻。時刻は Unix エポック(1970-01-01T00:00:00Z)からのミリ秒数で表されます。No

その他の 標準クレーム (Standard Claims) には、family_namegiven_namemiddle_namenicknamepreferred_usernameprofilewebsitegenderbirthdatezoneinfolocale などがあり、これらも profile スコープに含まれ、userinfo エンドポイントをリクエストする必要はありません。上記のクレームとの違いは、これらのクレームは値が空でない場合のみ返される点です。一方、上記のクレームは値が空の場合 null が返されます。

注記:

標準クレームと異なり、created_at および updated_at クレームは秒ではなくミリ秒を使用しています。

email

Claim nameType説明Needs userinfo?
emailstringユーザーのメールアドレスNo
email_verifiedbooleanメールアドレスが認証済みかどうかNo

phone

Claim nameType説明Needs userinfo?
phone_numberstringユーザーの電話番号No
phone_number_verifiedboolean電話番号が認証済みかどうかNo

address

アドレスクレームの詳細については OpenID Connect Core 1.0 を参照してください。

custom_data

Claim nameType説明Needs userinfo?
custom_dataobjectユーザーのカスタムデータYes

identities

Claim nameType説明Needs userinfo?
identitiesobjectユーザーのリンク済みアイデンティティYes
sso_identitiesarrayユーザーのリンク済み SSO アイデンティティYes

roles

Claim nameType説明Needs userinfo?
rolesstring[]ユーザーのロール (Roles)No

urn:logto:scope:organizations

Claim nameType説明Needs userinfo?
organizationsstring[]ユーザーが所属する組織 (Organizations) の IDNo
organization_dataobject[]ユーザーが所属する組織 (Organizations) のデータYes
注記:

これらの組織 (Organizations) クレームは、不透明トークン (Opaque token) を使用する場合にも userinfo エンドポイント経由で取得できます。ただし、不透明トークン (Opaque token) は組織トークン (Organization token) として組織固有リソースへのアクセスには使用できません。詳細は 不透明トークン (Opaque token) と組織 (Organizations) を参照してください。

urn:logto:scope:organization_roles

Claim nameType説明Needs userinfo?
organization_rolesstring[]ユーザーが所属する組織 (Organizations) のロール (Roles)。形式は <organization_id>:<role_name>No

パフォーマンスとデータサイズを考慮し、「Needs userinfo?」が「Yes」の場合、そのクレーム (Claim) は ID トークン (ID token) には表示されず、userinfo エンドポイント のレスポンスで返されます。

API リソース

まず 🔐 ロールベースのアクセス制御 (RBAC) を読むことをお勧めします。これにより、Logto の RBAC の基本概念と API リソースを適切に設定する方法を理解できます。

アプリで API リソースを設定する

API リソースを設定したら、アプリで Logto を設定する際にそれらを追加できます:

Program.cs
builder.Services.AddLogtoAuthentication(options =>
{
// ...
options.Resource = "https://<your-api-resource-indicator>";
});

各 API リソースには独自の権限 (スコープ) があります。

例えば、https://shopping.your-app.com/api リソースには shopping:readshopping:write の権限があり、https://store.your-app.com/api リソースには store:readstore:write の権限があります。

これらの権限を要求するには、アプリで Logto を設定する際にそれらを追加できます:

Program.cs
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 コールのためにアクセス トークン (Access token) や ID トークン (ID token) を取得する必要があります。GetTokenAsync メソッドを使用してトークンを取得できます:

var accessToken = await HttpContext.GetTokenAsync(LogtoParameters.Tokens.AccessToken);
var idToken = await HttpContext.GetTokenAsync(LogtoParameters.Tokens.IdToken);

トークンの有効期限について心配する必要はありません。認証 (Authentication) ミドルウェアが必要に応じて自動的にトークンをリフレッシュします。

注意:

認証 (Authentication) ミドルウェアはトークンを自動的にリフレッシュしますが、ユーザーオブジェクト内のクレーム (Claims) は、基盤となる OpenID Connect 認証 (Authentication) ハンドラーの制限により更新されません。 独自の認証 (Authentication) ハンドラーを実装すれば、この問題は解決できます。

上記のアクセス トークン (Access token) は、OpenID Connect の userinfo エンドポイント用の不透明トークン (Opaque token) であり、JWT ではありません。API リソースを指定している場合は、LogtoParameters.Tokens.AccessTokenForResource を使用して API リソース用のアクセス トークン (Access token) を取得する必要があります:

var accessToken = await HttpContext.GetTokenAsync(LogtoParameters.Tokens.AccessTokenForResource);

このトークンは、API リソースがオーディエンス (Audience) となる JWT になります。

さらなる読み物

エンドユーザーフロー:認証 (Authentication) フロー、アカウントフロー、組織フロー コネクターの設定 認可 (Authorization)