승인 및 증명으로 데이터 연결 보호

Firebase Data Connect는 다음을 통해 강력한 클라이언트 측 보안을 제공합니다.

  • 모바일 및 웹 클라이언트 승인
  • 개별 쿼리 및 변이 수준 승인 컨트롤
  • Firebase App Check를 사용한 앱 증명

Data Connect는 다음을 통해 이 보안을 확장합니다.

  • 서버 측 승인
  • IAM을 사용한 Firebase 프로젝트 및 Cloud SQL 사용자 보안

클라이언트 쿼리 및 변형 승인

Data ConnectFirebase Authentication와 완전히 통합되어 있으므로 사용자가 액세스할 수 있는 데이터 (인증)에 대한 설계에서 데이터에 액세스하는 사용자에 관한 풍부한 데이터 (인증)를 사용할 수 있습니다.

Data Connect는 쿼리 및 변이에 @auth 지시어를 제공하여 작업을 승인하는 데 필요한 인증 수준을 설정할 수 있습니다. 이 가이드에서는 @auth 지시문을 예시와 함께 소개합니다.

또한 Data Connect는 변이에 삽입된 쿼리의 실행을 지원하므로 데이터베이스에 저장된 추가 승인 기준을 검색하고 @check 지시어에서 해당 기준을 사용하여 묶인 변이가 승인되었는지 결정할 수 있습니다. 이 승인 사례에서 @redact 지시어를 사용하면 쿼리 결과가 와이어 프로토콜의 클라이언트에 반환되는지, 생성된 SDK에서 삽입된 쿼리가 생략되는지 제어할 수 있습니다. 예시와 함께 이러한 지시문의 소개를 확인하세요.

@auth 지시문 이해하기

@auth 지시어를 매개변수화하여 여러 사전 설정 액세스 수준 중 하나를 따를 수 있습니다. 이러한 액세스 수준은 여러 일반적인 액세스 시나리오를 포함합니다. 이러한 수준은 PUBLIC (모든 종류의 인증 없이 모든 클라이언트에서 쿼리 및 변형 허용)에서 NO_ACCESS (Firebase Admin SDK를 사용하는 권한이 있는 서버 환경 외부에서 쿼리 및 변형 허용 안 함)에 이르기까지 다양합니다. 이러한 각 수준은 Firebase Authentication에서 제공하는 인증 흐름과 관련이 있습니다.

수준 정의
PUBLIC 이 작업은 인증 여부와 관계없이 누구나 실행할 수 있습니다.
PUBLIC 이 작업은 인증 여부와 관계없이 누구나 실행할 수 있습니다.
USER_ANON Firebase Authentication로 익명으로 로그인한 사용자를 포함한 모든 식별된 사용자는 쿼리 또는 변형을 실행할 수 있습니다.
USER Firebase Authentication로 로그인한 모든 사용자는 익명 로그인 사용자를 제외하고 쿼리 또는 변형을 실행할 수 있습니다.
USER_EMAIL_VERIFIED 인증된 이메일 주소로 Firebase Authentication에 로그인한 사용자는 쿼리 또는 변형을 실행할 수 있습니다.
NO_ACCESS 이 작업은 Admin SDK 컨텍스트 외부에서 실행할 수 없습니다.

이러한 사전 설정 액세스 수준을 시작점으로 사용하여 서버에서 평가되는 where 필터와 Common Expression Language (CEL) 표현식을 사용하여 @auth 지시어에서 복잡하고 강력한 승인 검사를 정의할 수 있습니다.

@auth 지시문을 사용하여 일반적인 승인 시나리오 구현

사전 설정된 액세스 수준은 승인을 위한 시작점입니다.

USER 액세스 수준은 시작하기에 가장 유용한 기본 수준입니다.

완전한 보안 액세스는 USER 수준과 사용자 속성, 리소스 속성, 역할 및 기타 검사를 확인하는 필터와 표현식을 기반으로 합니다. USER_ANONUSER_EMAIL_VERIFIED 수준은 USER 사례의 변형입니다.

표현식 구문을 사용하면 작업과 함께 전달되는 인증 데이터를 나타내는 auth 객체를 사용하여 데이터를 평가할 수 있습니다. 여기에는 인증 토큰의 표준 데이터와 토큰의 맞춤 데이터가 모두 포함됩니다. auth 객체에서 사용할 수 있는 필드 목록은 참조 섹션을 참고하세요.

물론 PUBLIC가 처음부터 올바른 액세스 수준인 사용 사례도 있습니다. 액세스 수준은 항상 시작점이며 강력한 보안을 위해서는 추가 필터와 표현식이 필요합니다.

이제 이 가이드에서는 USERPUBLIC을 기반으로 빌드하는 방법을 보여주는 예를 제공합니다.

동기 부여 예시

다음 권장사항 예시에서는 특정 콘텐츠가 요금제 뒤에 잠겨 있는 블로그 플랫폼의 다음 스키마를 참조합니다.

이러한 플랫폼은 UsersPosts를 모델링할 가능성이 높습니다.

type User @table(key: "uid") {
  uid: String!
  name: String
  birthday: Date
  createdAt: Timestamp! @default(expr: "request.time")
}

type Post @table {
  author: User!
  text: String!
  # "one of 'draft', 'public', or 'pro'"
  visibility: String! @default(value: "draft")
  # "the time at which the post should be considered published. defaults to
  # immediately"
  publishedAt: Timestamp! @default(expr: "request.time")
  createdAt: Timestamp! @default(expr: "request.time")
  updatedAt: Timestamp! @default(expr: "request.time")
}

사용자 소유 리소스

다음과 같은 경우 리소스의 사용자 소유권(Posts의 소유권)을 테스트하는 필터와 표현식을 작성하는 것이 좋습니다.

다음 예에서는 표현식을 사용하여 인증 토큰의 데이터를 읽고 비교합니다. 일반적인 패턴은 where: {authorUid: {eq_expr: "auth.uid"}}와 같은 표현식을 사용하여 저장된 authorUid을 인증 토큰에 전달된 auth.uid(사용자 ID)와 비교하는 것입니다.

만들기

이 승인 관행은 후속 승인 테스트에서 비교할 수 있도록 인증 토큰의 auth.uid을 각 새 PostauthorUid 필드로 추가하는 것으로 시작합니다.

# Create a new post as the current user
mutation CreatePost($text: String!, $visibility: String) @auth(level: USER) {
  post_insert(data: {
    # set the author's uid to the current user uid
    authorUid_expr: "auth.uid"
    text: $text
    visibility: $visibility
  })
}
Update

클라이언트가 Post를 업데이트하려고 하면 전달된 auth.uid을 저장된 authorUid에 대해 테스트할 수 있습니다.

# Update one of the current user's posts
mutation UpdatePost($id: UUID!, $text: String, $visibility: String) @auth(level:USER) {
  post_update(
    # only update posts whose author is the current user
    first: { where: {
      id: {eq: $id}
      authorUid: {eq_expr: "auth.uid"}
    }}
    data: {
      text: $text
      visibility: $visibility
      # insert the current server time for updatedAt
      updatedAt_expr: "request.time"
    }
  )
}
삭제

삭제 작업을 승인하는 데도 동일한 기법이 사용됩니다.

# Delete one of the current user's posts
mutation DeletePost($id: UUID!) @auth(level: USER) {
  post_delete(
    # only delete posts whose author is the current user
    first: { where: {
      id: {eq: $id}
      authorUid: {eq_expr: "auth.uid"}
    }}
  )
}
# Common display information for a post
fragment DisplayPost on Post {
  id, text, createdAt, updatedAt
  author { uid, name }
}
목록
# List all posts belonging to the current user
query ListMyPosts @auth(level: USER) {
  posts(where: {
    userUid: {eq_expr: "auth.uid"}
  }) {
    # See the fragment above
    ...DisplayPost
    # also show visibility since it is user-controlled
    visibility
  }
}
가져오기
# Get a post only if it belongs to the current user
query GetMyPost($id: UUID!) @auth(level: USER) {
  post(key: {id: $id},
    first: {where: {
      id: {eq: $id}
      authorUid: {eq_expr: "auth.uid"}}
      }}, {
      # See the fragment above
      ...DisplayPost
      # also show visibility since it is user-controlled
      visibility
  }
}

데이터 필터링

Data Connect의 승인 시스템을 사용하면 PUBLIC과 같은 사전 설정된 액세스 수준과 결합된 정교한 필터를 작성할 수 있으며 인증 토큰의 데이터를 사용할 수도 있습니다.

승인 시스템을 사용하면 다음 예와 같이 기본 액세스 수준 없이 표현식만 사용할 수도 있습니다.

리소스 속성으로 필터링

여기서는 기본 보안 수준이 PUBLIC로 설정되어 있으므로 승인이 인증 토큰을 기반으로 하지 않습니다. 하지만 데이터베이스에서 레코드를 공개 액세스에 적합한 것으로 명시적으로 설정할 수 있습니다. 데이터베이스에 visibility이 'public'으로 설정된 Post 레코드가 있다고 가정해 보겠습니다.

# List all posts marked as 'public' visibility
query ListPublicPosts @auth(level: PUBLIC) {
  posts(where: {
    # Test that visibility is "public"
    visibility: {eq: "public"}
    # Only display articles that are already published
    publishedAt: {lt_expr: "request.time"}
  }) {
    # see the fragment above
    ...DisplayPost
  }
}
사용자 클레임으로 필터링

여기서는 인증 토큰을 전달하여 앱의 '프로' 요금제 사용자를 식별하는 맞춤 사용자 클레임을 설정했다고 가정합니다. 이 클레임은 인증 토큰의 auth.token.plan 필드로 표시됩니다. 표현식은 이 필드에 대해 테스트할 수 있습니다.

# List all public or pro posts, only permitted if user has "pro" plan claim
query ProListPosts @auth(expr: "auth.token.plan == 'pro'") {
  posts(where: {
    # display both public posts and "pro" posts
    visibility: {in: ['public', 'pro']},
    # only display articles that are already published
    publishedAt: {lt_expr: "request.time"},
  }) {
    # see the fragment above
    ...DisplayPost
    # show visibility so pro users can see which posts are pro\
    visibility
  }
}
주문 + 제한으로 필터링

또는 Post 레코드에서 visibility를 설정하여 '프로' 사용자가 사용할 수 있는 콘텐츠임을 식별했지만 데이터의 미리보기 또는 티저 등록정보의 경우 반환되는 레코드 수를 추가로 제한할 수 있습니다.

# Show 2 oldest Pro post as a preview
query ProTeaser @auth(level: USER) {
  posts(
    where: {
      # show only pro posts
      visibility: {eq: "pro"}
      # that have already been published more than 30 days ago
      publishedAt: {lt_time: {now: true, sub: {days: 30}}}
    },
    # order by publish time
    orderBy: [{publishedAt: DESC}],
    # only return two posts
    limit: 2
  ) {
    # See the fragment above
    ...DisplayPost
  }
}
역할별로 필터링

커스텀 클레임이 admin 역할을 정의하는 경우 이에 따라 작업을 테스트하고 승인할 수 있습니다.

# List all posts unconditionally iff the current user has an admin claim
query AdminListPosts @auth(expr: "auth.token.admin == true") {
  posts { ...DisplayPost }
}

승인 데이터를 조회하도록 @check@redact 지시문 추가

일반적인 승인 사용 사례는 데이터베이스에 맞춤 승인 역할을 저장하고(예: 특수 권한 테이블) 이러한 역할을 사용하여 데이터를 생성, 업데이트 또는 삭제하는 변형을 승인하는 것입니다.

승인 데이터 조회를 사용하면 userID에 따라 역할을 쿼리하고 CEL 표현식을 사용하여 변이가 승인되었는지 여부를 결정할 수 있습니다. 예를 들어 승인된 클라이언트가 영화 제목을 업데이트할 수 있는 UpdateMovieTitle 변이를 작성할 수 있습니다.

이 토론의 나머지 부분에서는 영화 리뷰 앱 데이터베이스가 MoviePermission 테이블에 승인 역할을 저장한다고 가정합니다.

# MoviePermission
# Suppose a user has an authorization role with respect to records in the Movie table
type MoviePermission @table(key: ["movie", "user"]) {
  movie: Movie! # implies another field: movieId: UUID!
  user: User!
  role: String!
}

변형에서 사용

다음 예시 구현에서 UpdateMovieTitle 변이에는 MoviePermission에서 데이터를 가져오는 query 필드와 작업이 안전하고 강력하도록 하는 다음 지시어가 포함됩니다.

  • 모든 승인 쿼리와 확인이 원자적으로 완료되거나 실패하도록 하는 @transaction 지시문
  • 응답에서 쿼리 결과를 생략하는 @redact 지시문입니다. 즉, 승인 확인은 Data Connect 서버에서 실행되지만 민감한 데이터는 클라이언트에 노출되지 않습니다.
  • 쿼리 결과에 대한 승인 로직을 평가하는 @check 지시어 쌍입니다. 예를 들어 특정 userID에 수정할 적절한 역할이 있는지 테스트합니다.

mutation UpdateMovieTitle($movieId: UUID!, $newTitle: String!) @auth(level: USER) @transaction {
  # Step 1: Query and check
  query @redact {
    moviePermission( # Look up a join table called MoviePermission with a compound key.
      key: {movieId: $movieId, userId_expr: "auth.uid"}
    # Step 1a: Use @check to test if the user has any role associated with the movie
    # Here the `this` binding refers the lookup result, i.e. a MoviePermission object or null
    # The `this != null` expression could be omitted since rejecting on null is default behavior
    ) @check(expr: "this != null", message: "You do not have access to this movie") {
      # Step 1b: Check if the user has the editor role for the movie
      # Next we execute another @check; now `this` refers to the contents of the `role` field
      role @check(expr: "this == 'editor'", message: "You must be an editor of this movie to update title")
    }
  }
  # Step 2: Act
  movie_update(id: $movieId, data: {
    title: $newTitle
  })
}

쿼리에서 사용

승인 데이터 조회를 사용하면 역할 또는 기타 제한사항에 따라 쿼리를 제한할 수도 있습니다.

MoviePermission 스키마를 사용하는 다음 예에서 쿼리는 요청자에게 영화를 수정할 수 있는 사용자를 볼 수 있는 적절한 '관리자' 역할이 있는지 확인합니다.

query GetMovieEditors($movieId: UUID!) @auth(level: PUBLIC) {
  moviePermission(key: { movieId: $movieId, userId_expr: "auth.uid" }) @redact {
    role @check(expr: "this == 'admin'", message: "You must be an admin to view all editors of a movie.")
  }
  moviePermissions(where: { movieId: { eq: $movieId }, role: { eq: "editor" } }) {
    user {
      id
      username
    }
  }
}

승인 시 피해야 할 안티패턴

이전 섹션에서는 @auth 지시어를 사용할 때 따라야 하는 패턴을 다루었습니다.

피해야 할 중요한 안티패턴도 알아야 합니다.

쿼리 및 변이 인수에서 사용자 속성 ID 및 인증 토큰 매개변수 전달 방지

Firebase Authentication는 인증 흐름을 표시하고 등록된 사용자 ID 및 인증 토큰에 저장된 여러 필드와 같은 인증 데이터를 안전하게 캡처하는 강력한 도구입니다.

쿼리 및 변이 인수에서 사용자 ID와 인증 토큰 데이터를 전달하는 것은 권장되지 않습니다.

# Antipattern!
# This incorrectly allows any user to view any other user's posts
query AllMyPosts($userId: String!) @auth(level: USER) {
  posts(where: {authorUid: {eq: $userId}}) {
    id, text, createdAt
  }
}

필터 없이 USER 액세스 수준 사용하지 않기

가이드에서 여러 번 설명한 것처럼 USER, USER_ANON, USER_EMAIL_VERIFIED와 같은 핵심 액세스 수준은 승인 확인의 기준이자 시작점으로, 필터와 표현식으로 강화됩니다. 요청을 실행하는 사용자를 확인하는 해당 필터나 표현식 없이 이러한 수준을 사용하는 것은 기본적으로 PUBLIC 수준을 사용하는 것과 같습니다.

# Antipattern!
# This incorrectly allows any user to view all documents
query ListDocuments @auth(level: USER) {
  documents {
    id
    title
    text
  }
}

프로토타입 제작 시 PUBLIC 또는 USER 액세스 수준 사용하지 않기

개발 속도를 높이기 위해 모든 작업을 PUBLIC 액세스 수준 또는 USER 액세스 수준으로 설정하여 모든 작업을 승인하고 코드를 빠르게 테스트하고 싶을 수 있습니다.

이 방식으로 초기 프로토타입을 만들었다면 NO_ACCESS에서 PUBLICUSER 수준의 프로덕션 준비 승인으로 전환하세요. 하지만 이 가이드에 표시된 대로 추가 로직을 추가하지 않고 PUBLIC 또는 USER로 배포하지는 마세요.

# Antipattern!
# This incorrectly allows anyone to delete any post
mutation DeletePost($id: UUID!) @auth(level: PUBLIC) {
  post: post_delete(
    id: $id,
  )
}

인증되지 않은 이메일 주소를 기반으로 승인하지 않기

특정 도메인의 사용자에게 액세스 권한을 부여하면 액세스를 제한할 수 있습니다. 하지만 로그인 중에 누구나 이메일을 소유하고 있다고 주장할 수 있습니다. Firebase 인증을 통해 인증된 이메일 주소에만 액세스 권한을 부여해야 합니다.

# Antipattern!
# Anyone can claim an email address during sign-in
mutation CreatePost($text: String!, $visibility: String) @auth(expr: "auth.token.email.endsWith('@example.com')") {
  post_insert(data: {
    # set the author's uid to the current user uid
    authorUid_expr: "auth.uid"
    text: $text
    visibility: $visibility
  })
}

auth.token.email_verified도 확인하세요.

mutation CreatePost($text: String!, $visibility: String) @auth(expr: "auth.token.email_veri&&fied  auth.token.email.endsWith('@example.com')") {
  post_insert(data: {
    # set the author's uid to the current user uid
    authorUid_expr: "auth.uid"
    text: $text
    visibility: $visibility
  })
}

Firebase CLI로 승인 감사

앞서 설명한 것처럼 PUBLIC, USER과 같은 사전 설정 액세스 수준은 강력한 승인의 시작점이며 추가 필터 및 표현식 기반 승인 확인과 함께 사용해야 합니다. 사용 사례를 신중하게 고려하지 않고 단독으로 사용해서는 안 됩니다.

Data Connect를 사용하면 Firebase CLI에서 firebase deploy를 사용하여 서버에 배포할 때 커넥터 코드를 분석하여 승인 전략을 감사할 수 있습니다. 이 감사를 사용하여 코드베이스를 검토할 수 있습니다.

커넥터를 배포하면 CLI에서 커넥터의 기존, 수정된, 새로운 작업 코드에 대한 평가를 출력합니다.

수정된 작업과 새 작업의 경우 새 작업에서 특정 액세스 수준을 사용하거나 기존 작업을 수정하여 해당 액세스 수준을 사용하는 경우 CLI에서 경고를 표시하고 확인을 요청합니다.

다음의 경우 항상 경고와 메시지가 표시됩니다.

  • PUBLIC

auth.uid를 사용하여 필터로 확장하지 않는 경우 다음 액세스 수준에서 경고와 메시지가 표시됩니다.

  • USER
  • USER_ANON
  • USER_EMAIL_VERIFIED

@auth(insecureReason:) 인수로 안전하지 않은 작업 경고 표시 억제

많은 경우 PUBLICUSER* 액세스 수준을 사용하는 것이 적절하다고 결론을 내릴 수 있습니다.

커넥터에 작업이 많이 포함된 경우 일반적으로 경고를 트리거하지만 올바른 액세스 수준이 있는 것으로 알려진 작업을 생략하는 더 명확하고 관련성 높은 보안 감사 출력이 필요할 수 있습니다.

@auth(insecureReason:)를 사용하여 이러한 작업에 대한 경고를 억제할 수 있습니다. 예를 들면 다음과 같습니다.

query listItem @auth(level: PUBLIC, insecureReason: "This operation is safe to expose to the public.")
  {
    items {
      id name
    }
  }

앱 증명에 Firebase App Check 사용

인증 및 승인은 Data Connect 보안의 중요한 구성요소입니다. 인증 및 승인과 앱 증명을 결합하면 매우 강력한 보안 솔루션을 만들 수 있습니다.

Firebase App Check를 통한 증명을 사용하면 앱을 실행하는 기기는 Data Connect 작업이 인증된 앱에서 발생하고 요청이 변조되지 않은 인증된 기기에서 발생했음을 증명하는 앱 또는 기기 증명 제공자를 사용합니다. 이 증명은 앱이 Data Connect에 대해 수행하는 모든 요청에 연결됩니다.

Data ConnectApp Check를 사용 설정하고 클라이언트 SDK를 앱에 포함하는 방법을 알아보려면 App Check 개요를 살펴보세요.

@auth(level) 지시문의 인증 수준

다음 표에는 모든 표준 액세스 수준과 그에 해당하는 CEL이 나와 있습니다. 인증 수준은 광범위한 수준부터 좁은 수준까지 나열됩니다. 각 수준에는 다음 수준과 일치하는 모든 사용자가 포함됩니다.

수준 정의
PUBLIC 이 작업은 인증 여부와 관계없이 누구나 실행할 수 있습니다.

고려사항: 모든 사용자가 데이터를 읽거나 수정할 수 있습니다. Firebase는 제품 또는 미디어 등록정보와 같이 공개적으로 탐색 가능한 데이터에 이 수준의 승인을 권장합니다. 권장사항 예시 및 대안을 참고하세요.

@auth(expr: "true")와 동일

@auth 필터와 표현식은 이 액세스 수준과 함께 사용할 수 없습니다. 이러한 표현식은 400 잘못된 요청 오류와 함께 실패합니다.
USER_ANON Firebase Authentication로 익명으로 로그인한 사용자를 포함한 모든 식별된 사용자는 쿼리 또는 변형을 실행할 수 있습니다.

참고: USER_ANONUSER의 상위 집합입니다.

고려사항: 이 수준의 승인을 위해 쿼리와 변형을 신중하게 설계해야 합니다. 이 수준에서는 사용자가 Authentication익명으로 로그인 (사용자 기기에만 연결된 자동 로그인)할 수 있으며, 데이터가 사용자에게 속하는지 여부와 같은 다른 검사는 자체적으로 실행하지 않습니다. 권장사항 예시 및 대안을 참고하세요.

Authentication 익명 로그인 흐름에서 uid를 발급하므로 USER_ANON 수준은
와 동일합니다. @auth(expr: "auth.uid != nil")
USER Firebase Authentication로 로그인한 모든 사용자는 익명 로그인 사용자를 제외하고 쿼리 또는 변형을 실행할 수 있습니다.

고려사항: 이 수준의 승인을 위해 쿼리와 변형을 신중하게 설계해야 합니다. 이 수준에서는 사용자가 Authentication로 로그인했는지 확인하기만 하며, 데이터가 사용자에게 속하는지 등 다른 확인은 자체적으로 실행하지 않습니다. 권장사항 예시 및 대안을 참고하세요.

@auth(expr: "auth.uid != nil && auth.token.firebase.sign_in_provider != 'anonymous'")"과 같습니다.
USER_EMAIL_VERIFIED 인증된 이메일 주소로 Firebase Authentication에 로그인한 사용자는 쿼리 또는 변형을 실행할 수 있습니다.

고려사항: 이메일 인증은 Authentication를 사용하여 실행되므로 더 강력한 Authentication 방법을 기반으로 합니다. 따라서 이 수준은 USER 또는 USER_ANON에 비해 보안이 강화됩니다. 이 수준에서는 사용자가 인증된 이메일로 Authentication에 로그인했는지 확인하기만 하며, 데이터가 사용자에게 속하는지 등 다른 확인은 자체적으로 실행하지 않습니다. 권장사항 예시 및 대안을 참고하세요.

@auth(expr: "auth.uid != nil && auth.token.email_verified")"과 같습니다.
NO_ACCESS 이 작업은 Admin SDK 컨텍스트 외부에서 실행할 수 없습니다.

@auth(expr: "false")과 같습니다.

@auth(expr)의 CEL 참조

이 가이드의 다른 곳에 나온 예와 같이 Common Expression Language (CEL)에 정의된 표현식을 사용하여 @auth(expr:)@check 지시어를 통해 Data Connect의 승인을 제어할 수 있습니다.

이 섹션에서는 이러한 지시문의 표현식을 만드는 데 관련된 CEL 구문을 다룹니다.

CEL에 대한 전체 참조 정보는 CEL 사양에 제공됩니다.

쿼리 및 변형에 전달된 변수 테스트

@auth(expr) 구문을 사용하면 쿼리 및 변형에서 변수에 액세스하고 변수를 테스트할 수 있습니다.

예를 들어 vars.status을 사용하여 $status와 같은 작업 변수를 포함할 수 있습니다.

mutation Update($id: UUID!, $status: Any) @auth(expr: "has(vars.status)")

표현식에 사용할 수 있는 데이터: request, response, this

데이터는 다음 용도로 사용됩니다.

  • @auth(expr:)@check(expr:) 지시문의 CEL 표현식으로 평가
  • 서버 표현식(<field>_expr)을 사용한 할당

@auth(expr:)@check(expr:) CEL 표현식은 다음을 평가할 수 있습니다.

  • request.operationName
  • vars (request.variables의 별칭)
  • auth (request.auth의 별칭)

변형에서는 다음의 콘텐츠에 액세스하고 할당할 수 있습니다.

  • response (다단계 로직에서 부분 결과를 확인하는 경우)

또한 @check(expr:) 표현식은 다음을 평가할 수 있습니다.

  • this (현재 필드의 값)
  • response (다단계 로직에서 부분 결과를 확인하는 경우)

request.operationName 바인딩

request.operarationName 바인딩은 작업 유형(쿼리 또는 변형)을 저장합니다.

vars 바인딩 (request.vars)

vars 바인딩을 사용하면 표현식에서 쿼리 또는 변형에 전달된 모든 변수에 액세스할 수 있습니다.

표현식에서 vars.<variablename>을 정규화된 request.variables.<variablename>의 별칭으로 사용할 수 있습니다.

# The following are equivalent
mutation StringType($v: String!) @auth(expr: "vars.v == 'hello'")
mutation StringType($v: String!) @auth(expr: "request.variables.v == 'hello'")

auth 바인딩 (request.auth)

Authentication는 데이터 액세스를 요청하는 사용자를 식별하고 해당 정보를 표현식에서 사용할 수 있는 바인딩으로 제공합니다.

필터와 표현식에서 authrequest.auth의 별칭으로 사용할 수 있습니다.

인증 바인딩에는 다음 정보가 포함됩니다.

  • uid: 요청하는 사용자에게 할당된 순 사용자 ID입니다.
  • token: Authentication에서 수집한 값의 맵입니다.

auth.token의 콘텐츠에 관한 자세한 내용은 인증 토큰의 데이터를 참고하세요.

response 바인딩

response 바인딩에는 데이터가 어셈블되는 동안 쿼리 또는 변형에 대한 응답으로 서버에서 어셈블되는 데이터가 포함됩니다.

작업이 진행되면서 각 단계가 성공적으로 완료되면 response에는 성공적으로 완료된 단계의 응답 데이터가 포함됩니다.

response 바인딩은 중첩된 필드와 (해당하는 경우) 삽입된 쿼리를 포함하여 연결된 작업의 형태에 따라 구조화됩니다.

삽입된 쿼리 응답 데이터에 액세스할 때 필드에는 삽입된 쿼리에서 요청된 데이터에 따라 모든 데이터 유형이 포함될 수 있습니다. _insert_delete와 같은 변형 필드에서 반환된 데이터에 액세스할 때 UUID 키, 삭제 수, null이 포함될 수 있습니다 (변형 참조 참고).

예를 들면 다음과 같습니다.

  • 삽입된 쿼리가 포함된 변이에서 response 바인딩에는 response.query.<fieldName>.<fieldName>....의 조회 데이터가 포함됩니다. 이 경우 response.query.todoListresponse.query.todoList.priority입니다.
mutation CheckTodoPriority(
  $uniqueListName: String!
) {
  # This query is identified as `response.query`
  query @check(expr: "response.query.todoList.priority == 'high'", message: "This list is not for high priority items!") {
    # This field is identified as `response.query.todoList`
    todoList(where: { name: $uniqueListName }) {
      # This field is identified as `response.query.todoList.priority`
      priority
    }
  }
}
  • 예를 들어 _insert 필드가 여러 개인 다단계 변이에서 response 바인딩은 response.<fieldName>.<fieldName>....(이 경우 response.todoList_insert.id)에 부분 데이터를 포함합니다.
mutation CreateTodoListWithFirstItem(
  $listName: String!,
  $itemContent: String!
) @transaction {
  # Step 1
  todoList_insert(data: {
    id_expr: "uuidV4()",
    name: $listName,
  })
  # Step 2:
  todo_insert(data: {
    listId_expr: "response.todoLis<t_insert.id" # -- Grab the newly generated ID from the partial response so far.
    content: $itemContent,
  })
}

this 바인딩

this 바인딩은 @check 지시어가 연결된 필드로 평가됩니다. 기본적인 경우 단일 값 쿼리 결과를 평가할 수 있습니다.

mutation UpdateMovieTitle (
  $movieId: UUID!,
  $newTitle: String!)
  @auth(level: USER)
  @transaction {
  # Step 1: Query and check
  query @redact {
    moviePermission( # Look up a join table called MoviePermission with a compound key.
      key: {movieId: $movieId, userId_expr: "auth.uid"}
    ) {
      # Check if the user has the editor role for the movie. `this` is the string value of `role`.
      # If the parent moviePermission is null, the @check will also fail automatically.
      role @check(expr: "this == 'editor'", message: "You must be an editor of this movie to update title")
    }
  }
  # Step 2: Act
  movie_update(id: $movieId, data: {
    title: $newTitle
  })
}

상위 요소가 목록이므로 반환된 필드가 여러 번 발생하면 각 발생은 각 값에 바인딩된 this로 테스트됩니다.

특정 경로에서 상위 요소가 null 또는 []이면 필드에 도달하지 못하고 해당 경로에 대한 CEL 평가가 건너뜁니다. 즉, thisnull 또는 null이 아닌 경우에만 평가가 이루어지며 undefined인 경우에는 평가가 이루어지지 않습니다.

필드 자체가 목록 또는 객체인 경우 this는 다음 예와 같이 동일한 구조(객체의 경우 선택된 모든 하위 요소 포함)를 따릅니다.

mutation UpdateMovieTitle2($movieId: UUID!, $newTitle: String!) @auth(level: USER) @transaction {
  # Step 1: Query and check
  query {
    moviePermissions( # Now we query for a list of all matching MoviePermissions.
      where: {movieId: {eq: $movieId}, userId: {eq_expr: "auth.uid"}}
    # This time we execute the @check on the list, so `this` is the list of objects.
    # We can use the `.exists` macro to check if there is at least one matching entry.
    ) @check(expr: "this.exists(p, p.role == 'editor')", message: "You must be an editor of this movie to update title") {
      role
    }
  }
  # Step 2: Act
  movie_update(id: $movieId, data: {
    title: $newTitle
  })
}

복잡한 표현식 구문

&&|| 연산자와 결합하여 더 복잡한 표현식을 작성할 수 있습니다.

mutation UpsertUser($username: String!) @auth(expr: "(auth != n&&ull)  (vars.username == ';joe')")

다음 섹션에서는 사용 가능한 모든 연산자를 설명합니다.

연산자 및 연산자 우선순위

다음 표를 통해 연산자와 연산자 우선순위를 참조할 수 있습니다.

ab, 필드 f, 색인 i는 임의로 주어진 표현식입니다.

연산자 설명 결합
a[i] a() a.f 색인, 호출, 필드 액세스 왼쪽에서 오른쪽으로
!a -a 단항 부정 오른쪽에서 왼쪽으로
a/b a%b a*b 곱셈 연산자 왼쪽에서 오른쪽으로
a+b a-b 덧셈 연산자 왼쪽에서 오른쪽으로
a>b a>=b a<b a<=b 관계 연산자 왼쪽에서 오른쪽으로
a in b 목록 또는 맵에 존재 왼쪽에서 오른쪽으로
type(a) == t 유형 비교: t은 bool, int, float, number, string, list, map, timestamp, duration일 수 있습니다. 왼쪽에서 오른쪽으로
a==b a!=b 비교 연산자 왼쪽에서 오른쪽으로
a && b 조건부 AND 왼쪽에서 오른쪽으로
a || b 조건부 OR 왼쪽에서 오른쪽으로
a ? true_value : false_value 3항 표현식 왼쪽에서 오른쪽으로

인증 토큰의 데이터

auth.token 객체에는 다음 값이 포함될 수 있습니다.

필드 설명
email 계정과 연결된 이메일 주소(있는 경우)입니다.
email_verified true는 사용자가 email 주소에 대한 액세스 권한이 있는지 확인한 경우입니다. 일부 제공업체는 자동으로 자체 이메일 주소를 확인합니다.
phone_number 계정과 연결된 전화번호(있는 경우)입니다.
name 사용자의 표시 이름(설정된 경우)입니다.
sub 사용자의 Firebase UID입니다. 프로젝트 내에서 고유합니다.
firebase.identities 사용자 계정과 연결된 모든 ID의 사전입니다. 사전의 키는 email, phone, google.com, facebook.com, github.com, twitter.com일 수 있습니다. 사전의 값은 계정과 연결된 각 ID 공급업체의 고유한 식별자 배열입니다. 예를 들어 auth.token.firebase.identities["google.com"][0]에는 계정과 연결된 첫 번째 Google 사용자 ID가 포함됩니다.
firebase.sign_in_provider 토큰을 얻기 위해 사용된 로그인 제공업체입니다. 문자열 custom, password, phone, anonymous, google.com, facebook.com, github.com, twitter.com 중 하나일 수 있습니다.
firebase.tenant 계정과 연결된 tenantId(있는 경우)입니다. 예를 들면 tenant2-m6tyz입니다.

JWT ID 토큰의 추가 필드

다음 auth.token 필드에도 액세스할 수 있습니다.

커스텀 토큰 클레임
alg 알고리즘 "RS256"
iss 발급자 프로젝트의 서비스 계정 이메일 주소
sub 제목 프로젝트의 서비스 계정 이메일 주소
aud 잠재고객 "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
iat 발급 시간 Unix epoch를 기준으로 하는 현재 시간(초)
exp 만료 시간 Unix epoch를 기준으로 하는 토큰 만료 시간(초). iat보다 최대 3,600초 길어질 수 있습니다.
참고: 이 항목은 커스텀 토큰 자체의 만료 시간만 제어합니다. signInWithCustomToken()으로 사용자가 로그인한 후에는 세션이 무효화되거나 사용자가 로그아웃할 때까지 기기에서 로그인 상태가 유지됩니다.
<claims>(선택사항) 토큰에 포함할 선택적 맞춤 클레임으로, 표현식의 auth.token (또는 request.auth.token)을 통해 액세스할 수 있습니다. 예를 들어 adminClaim이라는 맞춤 클레임을 만드는 경우 auth.token.adminClaim을 사용하여 액세스할 수 있습니다.

다음 단계