跳到主要内容

Angularセキュリティ

AngularにはセキュリティがDNAレベルで組み込まれており、React/Next.jsでは手動で対応が必要な多くのセキュリティ対策がデフォルトで提供されます。大規模エンタープライズ開発では、これらの組み込みセキュリティ機能が大きなメリットとなります。

1. XSS(クロスサイトスクリプティング)防止

デフォルトのサニタイゼーション

Angularはテンプレート内のすべての値を自動的にエスケープします。{{ }} 補間式はHTMLエンティティに変換されるため、スクリプトの注入を防ぎます。

// コンポーネント
@Component({
template: `<p>{{ userInput }}</p>`
})
export class SafeComponent {
userInput = '<script>alert("XSS")</script>';
// テンプレートには &lt;script&gt;alert("XSS")&lt;/script&gt; と表示される
}

Reactの dangerouslySetInnerHTML とは異なり、Angularでは危険な操作に明示的なオプトインが必要です。

[innerHTML] バインディングのサニタイズ

[innerHTML] バインディングを使う場合も、Angularは自動的にHTMLをサニタイズします。<script> タグや危険な属性(onerror など)は除去されます。

@Component({
template: `<div [innerHTML]="htmlContent"></div>`
})
export class HtmlComponent {
// <script> タグは自動的に除去される
htmlContent = '<b>太字</b><script>alert("XSS")</script>';
// 出力: <b>太字</b>
}

DomSanitizer サービス

信頼できるコンテンツを明示的に挿入する場合は DomSanitizer を使います。ただし、誤用するとXSS脆弱性が生じるため注意が必要です。

import { Component } from '@angular/core';
import { DomSanitizer, SafeHtml, SafeUrl } from '@angular/platform-browser';

@Component({
selector: 'app-trusted',
template: `
<div [innerHTML]="trustedHtml"></div>
<a [href]="trustedUrl">リンク</a>
`
})
export class TrustedComponent {
trustedHtml: SafeHtml;
trustedUrl: SafeUrl;

constructor(private sanitizer: DomSanitizer) {
// ⚠️ 信頼できるソースからのコンテンツのみに使用すること
this.trustedHtml = this.sanitizer.bypassSecurityTrustHtml(
'<b>管理者が承認したコンテンツ</b>'
);
// ⚠️ ユーザー入力値には絶対に使わない
this.trustedUrl = this.sanitizer.bypassSecurityTrustUrl(
'https://example.com/safe-page'
);
}
}
メソッド用途リスク
bypassSecurityTrustHtmlHTMLの挿入スクリプト注入
bypassSecurityTrustUrlhref/src の URLjavascript: スキーム
bypassSecurityTrustResourceUrliframe/script の URL外部コード読み込み
bypassSecurityTrustStyleインラインスタイルCSSインジェクション
注意

bypassSecurityTrust* はユーザー入力値に対して絶対に使用しないでください。サーバー側で検証・サニタイズ済みのコンテンツのみに限定してください。

2. CSRFトークン(XSRF)対策

HttpClient の組み込み XSRF 対策

Angularの HttpClientCookie ベースの XSRF 対策を組み込みでサポートしています。サーバーが XSRF-TOKEN という名前の Cookie を設定すると、HttpClient は自動的に X-XSRF-TOKEN ヘッダーとして各リクエストに付加します。

// app.config.ts
import { provideHttpClient, withXsrfConfiguration } from '@angular/common/http';

export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
withXsrfConfiguration({
cookieName: 'XSRF-TOKEN', // サーバーが設定するクッキー名(デフォルト値)
headerName: 'X-XSRF-TOKEN', // リクエストに付加するヘッダー名(デフォルト値)
})
)
]
};

サーバー側の設定例(ASP.NET Core)

// Program.cs
builder.Services.AddAntiforgery(options =>
{
options.Cookie.Name = "XSRF-TOKEN";
options.HeaderName = "X-XSRF-TOKEN";
options.Cookie.SameSite = SameSiteMode.Strict;
});

// XSRF-TOKEN クッキーをAngularが読めるようにする(HttpOnly=false)
app.Use(async (context, next) =>
{
var antiforgery = context.RequestServices.GetRequiredService<IAntiforgery>();
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken!,
new CookieOptions { HttpOnly = false });
await next();
});
备注

React/Next.jsでは CSRF 対策を手動で実装する必要がありますが、Angularでは HttpClient が標準でこの仕組みを提供します。

3. Content Security Policy (CSP)

CSP の基本設定

CSP(Content Security Policy)は、ブラウザに対してリソース読み込みのポリシーを指示するHTTPヘッダーです。XSSの二次被害(スクリプトの実行)を防ぎます。

# サーバー側のHTTPヘッダー設定例
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{RANDOM_NONCE}';
style-src 'self' 'nonce-{RANDOM_NONCE}';
img-src 'self' data: https://trusted-cdn.example.com;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';

Nonce ベースの CSP(Angular 推奨)

Angular 16+ では nonce ベースの CSP を推奨しています。サーバーサイドで生成したランダムな nonce 値をAngularに渡すことで、インラインスタイルを CSP に準拠した形で使用できます。

<!-- index.html: Angular が nonce 属性を読み取る -->
<app-root ngCspNonce="{SERVER_GENERATED_NONCE}"></app-root>
// Angular 側: CSP nonce の設定
import { CSP_NONCE } from '@angular/core';

export const appConfig: ApplicationConfig = {
providers: [
{
provide: CSP_NONCE,
useValue: globalThis.myRandomNonce, // サーバーから渡された nonce 値
}
]
};

ViewEncapsulation と style-src

AngularのデフォルトViewEncapsulation(Emulated)はインラインスタイルを注入するため、style-src 'unsafe-inline' が必要になる場合があります。nonce を使うとこの問題を解消できます。

// コンポーネントレベルでの設定
@Component({
selector: 'app-root',
encapsulation: ViewEncapsulation.ShadowDom, // Shadow DOM を使用してスタイルを分離
})
export class AppComponent {}

4. 信頼できるタイプ(Trusted Types)

Trusted Types API の概要

Trusted Types は、DOM操作を型安全にすることでXSSを防ぐブラウザAPIです。innerHTMLeval などの危険なシンクへの文字列直接代入を禁止し、信頼できるオブジェクトのみを受け付けるようにします。

Angular の Trusted Types サポート

Angular は組み込みで Trusted Types に対応しており、フレームワーク内部の DOM 操作はすべて Trusted Types ポリシーを通して行われます。

# CSP ヘッダーで Trusted Types を有効化
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types angular
// Angular の Trusted Types ポリシー名は 'angular'
// カスタムポリシーが必要な場合
const policy = trustedTypes.createPolicy('myAppPolicy', {
createHTML: (input: string) => {
// 独自のサニタイズロジック
return DOMPurify.sanitize(input);
}
});
备注

Trusted Types のサポート状況はブラウザによって異なります(主要ブラウザでは対応済み)。ポリフィルの使用も検討してください。

5. 認証・認可パターン

HTTP Interceptor での認証トークン管理

// auth.interceptor.ts
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';

export const authInterceptor: HttpInterceptorFn = (req, next) => {
const authService = inject(AuthService);
const token = authService.getToken();

if (token) {
const authReq = req.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
return next(authReq);
}
return next(req);
};

// app.config.ts に登録
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(withInterceptors([authInterceptor]))
]
};

ルートガードによる画面レベルの認可

// auth.guard.ts(関数型ガード - Angular 15+)
import { inject } from '@angular/core';
import { Router, CanActivateFn } from '@angular/router';
import { AuthService } from './auth.service';

export const authGuard: CanActivateFn = (route, state) => {
const authService = inject(AuthService);
const router = inject(Router);

if (authService.isAuthenticated()) {
return true;
}
// 未認証時はログインページへリダイレクト
return router.createUrlTree(['/login'], {
queryParams: { returnUrl: state.url }
});
};

// app.routes.ts
export const routes: Routes = [
{
path: 'dashboard',
component: DashboardComponent,
canActivate: [authGuard]
},
{
path: 'admin',
loadComponent: () => import('./admin/admin.component'),
canActivate: [authGuard, adminGuard] // 複数ガードの組み合わせ
}
];

カスタムディレクティブによる要素レベルの認可

// has-role.directive.ts
@Directive({
selector: '[appHasRole]',
standalone: true
})
export class HasRoleDirective implements OnInit {
@Input('appHasRole') requiredRole!: string | string[];

constructor(
private templateRef: TemplateRef<unknown>,
private viewContainer: ViewContainerRef,
private authService: AuthService
) {}

ngOnInit() {
const roles = Array.isArray(this.requiredRole)
? this.requiredRole
: [this.requiredRole];

if (this.authService.hasAnyRole(roles)) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
}
}

// 使用例
// <button *appHasRole="'admin'">管理者専用ボタン</button>
// <div *appHasRole="['editor', 'admin']">編集・管理者向けコンテンツ</div>

JWT トークンリフレッシュパターン

// auth.interceptor.ts(トークンリフレッシュ付き)
import { catchError, switchMap, throwError } from 'rxjs';

export const authInterceptor: HttpInterceptorFn = (req, next) => {
const authService = inject(AuthService);

return next(addToken(req, authService.getToken())).pipe(
catchError(error => {
if (error.status === 401 && !req.url.includes('/auth/refresh')) {
// アクセストークン期限切れ → リフレッシュを試みる
return authService.refreshToken().pipe(
switchMap(newToken => {
return next(addToken(req, newToken));
}),
catchError(refreshError => {
authService.logout(); // リフレッシュも失敗したらログアウト
return throwError(() => refreshError);
})
);
}
return throwError(() => error);
})
);
};

function addToken(req: HttpRequest<unknown>, token: string | null) {
return token ? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }) : req;
}
备注

React との比較: Reactでは認証ロジックをカスタムフックやコンテキストで個別実装が必要ですが、Angularでは**インターセプター(HTTPレイヤー)+ガード(ルートレイヤー)+ディレクティブ(UIレイヤー)**という統一的なパターンで認証・認可を管理できます。

6. セキュアなAPIリクエスト

// HttpClient での withCredentials 設定
@Injectable({ providedIn: 'root' })
export class ApiService {
constructor(private http: HttpClient) {}

getData() {
return this.http.get('/api/data', {
withCredentials: true // HttpOnly Cookie を送信
});
}
}

// インターセプターで一括設定する場合
export const credentialsInterceptor: HttpInterceptorFn = (req, next) => {
const credentialReq = req.clone({ withCredentials: true });
return next(credentialReq);
};

CORS とプリフライトリクエスト

CORS(Cross-Origin Resource Sharing)はサーバー側の設定です。Angularアプリからのリクエストが別オリジンに対して行われる場合、ブラウザはプリフライトリクエスト(OPTIONS)を送ります。

// サーバー側(ASP.NET Core)のCORS設定例
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAngularApp", policy =>
{
policy.WithOrigins("https://yourapp.example.com") // ワイルドカード(*) は使わない
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials(); // withCredentials を使う場合は必須
});
});
注意

AllowAnyOrigin()AllowCredentials() を同時に使うことはできません(セキュリティ上の理由で禁止されています)。具体的なオリジンを指定してください。

7. セキュリティ監査と依存関係

npm audit の活用

# 脆弱性のチェック
npm audit

# 自動修正(パッチバージョンのみ)
npm audit fix

# 詳細なレポート(JSON形式)
npm audit --json

# CI/CD パイプラインでの使用例(重大な脆弱性があればビルドを失敗させる)
npm audit --audit-level=high

ng update による自動パッチ適用

# Angularおよび依存関係の更新確認
ng update

# Angularコアの更新
ng update @angular/core @angular/cli

# すべての互換性のある更新を適用
ng update --all

ng update はメジャーバージョン更新時にマイグレーションスクリプトを自動実行するため、手動修正が大幅に削減されます。

依存関係の脆弱性管理

// package.json: 依存関係のバージョンを厳密に管理
{
"dependencies": {
"@angular/core": "~18.0.0" // チルダ(~)でパッチバージョンのみ自動更新
}
}
# Snyk などのサードパーティツールとの統合
npx snyk test
npx snyk monitor

# GitHub Dependabot の活用(.github/dependabot.yml)
# npm パッケージの自動PRを定期的に作成

セキュリティ関連のAngular設定チェックリスト

項目推奨設定
bypassSecurityTrust* の使用ユーザー入力には絶対使用禁止
XSRF 保護withXsrfConfiguration() で有効化
CSP ヘッダーnonce ベースで設定
withCredentials必要な場合のみ有効化
CORS具体的なオリジンを指定
依存関係定期的に npm audit + ng update
Trusted TypesCSP ヘッダーで有効化推奨