Firebase Summit에서 발표된 모든 내용을 살펴보고 Firebase로 앱을 빠르게 개발하고 안심하고 앱을 실행하는 방법을 알아보세요. 자세히 알아보기

사용자 세션 관리

컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.

Firebase 인증 세션은 오래 지속됩니다. 사용자가 로그인할 때마다 사용자 자격 증명이 Firebase 인증 백엔드로 전송되고 Firebase ID 토큰(JWT) 및 새로 고침 토큰으로 교환됩니다. Firebase ID 토큰은 수명이 짧고 1시간 동안 지속됩니다. 새로 고침 토큰을 사용하여 새 ID 토큰을 검색할 수 있습니다. 새로 고침 토큰은 다음 중 하나가 발생한 경우에만 만료됩니다.

  • 사용자가 삭제됨
  • 사용자가 비활성화되었습니다.
  • 사용자에 대한 주요 계정 변경이 감지되었습니다. 여기에는 비밀번호 또는 이메일 주소 업데이트와 같은 이벤트가 포함됩니다.

Firebase Admin SDK는 지정된 사용자의 갱신 토큰을 취소하는 기능을 제공합니다. 또한 ID 토큰 해지 여부를 확인하는 API도 제공됩니다. 이러한 기능을 사용하면 사용자 세션을 더 많이 제어할 수 있습니다. SDK는 세션이 의심스러운 상황에서 사용되지 않도록 제한을 추가하는 기능과 잠재적인 토큰 도난으로부터 복구하는 메커니즘을 제공합니다.

새로 고침 토큰 취소

사용자가 장치를 분실하거나 도난당했다고 보고하면 사용자의 기존 새로 고침 토큰을 취소할 수 있습니다. 마찬가지로 일반적인 취약점을 발견하거나 활성 토큰의 대규모 누출이 의심되는 경우 listUsers API를 사용하여 모든 사용자를 조회하고 지정된 프로젝트에 대한 토큰을 취소할 수 있습니다.

비밀번호 재설정은 사용자의 기존 토큰도 취소합니다. 그러나 Firebase 인증 백엔드는 이 경우 자동으로 취소를 처리합니다. 해지 시 사용자는 로그아웃되고 재인증하라는 메시지가 표시됩니다.

다음은 Admin SDK를 사용하여 지정된 사용자의 새로고침 토큰을 취소하는 구현 예입니다. Admin SDK를 초기화하려면 설정 페이지 의 지침을 따르세요.

노드.js

// Revoke all refresh tokens for a specified user for whatever reason.
// Retrieve the timestamp of the revocation, in seconds since the epoch.
getAuth()
  .revokeRefreshTokens(uid)
  .then(() => {
    return getAuth().getUser(uid);
  })
  .then((userRecord) => {
    return new Date(userRecord.tokensValidAfterTime).getTime() / 1000;
  })
  .then((timestamp) => {
    console.log(`Tokens revoked at: ${timestamp}`);
  });

자바

FirebaseAuth.getInstance().revokeRefreshTokens(uid);
UserRecord user = FirebaseAuth.getInstance().getUser(uid);
// Convert to seconds as the auth_time in the token claims is in seconds too.
long revocationSecond = user.getTokensValidAfterTimestamp() / 1000;
System.out.println("Tokens revoked at: " + revocationSecond);

파이썬

# Revoke tokens on the backend.
auth.revoke_refresh_tokens(uid)
user = auth.get_user(uid)
# Convert to seconds as the auth_time in the token claims is in seconds.
revocation_second = user.tokens_valid_after_timestamp / 1000
print('Tokens revoked at: {0}'.format(revocation_second))

가다

client, err := app.Auth(ctx)
if err != nil {
	log.Fatalf("error getting Auth client: %v\n", err)
}
if err := client.RevokeRefreshTokens(ctx, uid); err != nil {
	log.Fatalf("error revoking tokens for user: %v, %v\n", uid, err)
}
// accessing the user's TokenValidAfter
u, err := client.GetUser(ctx, uid)
if err != nil {
	log.Fatalf("error getting user %s: %v\n", uid, err)
}
timestamp := u.TokensValidAfterMillis / 1000
log.Printf("the refresh tokens were revoked at: %d (UTC seconds) ", timestamp)

씨#

await FirebaseAuth.DefaultInstance.RevokeRefreshTokensAsync(uid);
var user = await FirebaseAuth.DefaultInstance.GetUserAsync(uid);
Console.WriteLine("Tokens revoked at: " + user.TokensValidAfterTimestamp);

ID 토큰 해지 감지

Firebase ID 토큰은 상태 비저장 JWT이므로 Firebase 인증 백엔드에서 토큰 상태를 요청해야만 토큰이 취소되었는지 확인할 수 있습니다. 이러한 이유로 서버에서 이 검사를 수행하는 것은 비용이 많이 드는 작업이며 추가 네트워크 왕복이 필요합니다. Admin SDK를 사용하여 확인하는 대신 취소를 확인하는 Firebase 보안 규칙을 설정하면 이 네트워크 요청을 방지할 수 있습니다.

Firebase 보안 규칙에서 ID 토큰 취소 감지

보안 규칙을 사용하여 ID 토큰 취소를 감지하려면 먼저 일부 사용자별 메타데이터를 저장해야 합니다.

Firebase 실시간 데이터베이스에서 사용자별 메타데이터를 업데이트합니다.

새로 고침 토큰 해지 타임스탬프를 저장합니다. Firebase 보안 규칙을 통해 ID 토큰 취소를 추적하는 데 필요합니다. 이것은 데이터베이스 내에서 효율적인 검사를 허용합니다. 아래 코드 샘플에서는 이전 섹션 에서 얻은 uid 및 해지 시간을 사용합니다.

노드.js

const metadataRef = getDatabase().ref('metadata/' + uid);
metadataRef.set({ revokeTime: utcRevocationTimeSecs }).then(() => {
  console.log('Database updated successfully.');
});

자바

DatabaseReference ref = FirebaseDatabase.getInstance().getReference("metadata/" + uid);
Map<String, Object> userData = new HashMap<>();
userData.put("revokeTime", revocationSecond);
ref.setValueAsync(userData);

파이썬

metadata_ref = firebase_admin.db.reference("metadata/" + uid)
metadata_ref.set({'revokeTime': revocation_second})

Firebase 보안 규칙에 확인 추가

이 검사를 시행하려면 사용자당 해지 시간을 저장하는 클라이언트 쓰기 액세스 권한이 없는 규칙을 설정하십시오. 이전 예와 같이 마지막 해지 시간의 UTC 타임스탬프로 업데이트할 수 있습니다.

{
  "rules": {
    "metadata": {
      "$user_id": {
        // this could be false as it is only accessed from backend or rules.
        ".read": "$user_id === auth.uid",
        ".write": "false",
      }
    }
  }
}

인증된 액세스가 필요한 모든 데이터에는 다음 규칙이 구성되어 있어야 합니다. 이 논리는 해지되지 않은 ID 토큰이 있는 인증된 사용자만 보호된 데이터에 액세스할 수 있도록 허용합니다.

{
  "rules": {
    "users": {
      "$user_id": {
        ".read": "auth != null && $user_id === auth.uid && (
            !root.child('metadata').child(auth.uid).child('revokeTime').exists()
          || auth.token.auth_time > root.child('metadata').child(auth.uid).child('revokeTime').val()
        )",
        ".write": "auth != null && $user_id === auth.uid && (
            !root.child('metadata').child(auth.uid).child('revokeTime').exists()
          || auth.token.auth_time > root.child('metadata').child(auth.uid).child('revokeTime').val()
        )",
      }
    }
  }
}

SDK에서 ID 토큰 해지를 감지합니다.

서버에서 새로 고침 토큰 취소 및 ID 토큰 유효성 검사를 위해 다음 논리를 구현합니다.

사용자의 ID 토큰을 확인하려면 추가 checkRevoked 부울 플래그를 verifyIdToken 에 전달해야 합니다. 사용자의 토큰이 취소되면 사용자는 클라이언트에서 로그아웃하거나 Firebase 인증 클라이언트 SDK에서 제공하는 재인증 API를 사용하여 재인증을 요청해야 합니다.

플랫폼용 Admin SDK를 초기화하려면 설정 페이지 의 지침을 따르세요. ID 토큰 검색의 예는 verifyIdToken 섹션에 있습니다.

노드.js

// Verify the ID token while checking if the token is revoked by passing
// checkRevoked true.
let checkRevoked = true;
getAuth()
  .verifyIdToken(idToken, checkRevoked)
  .then((payload) => {
    // Token is valid.
  })
  .catch((error) => {
    if (error.code == 'auth/id-token-revoked') {
      // Token has been revoked. Inform the user to reauthenticate or signOut() the user.
    } else {
      // Token is invalid.
    }
  });

자바

try {
  // Verify the ID token while checking if the token is revoked by passing checkRevoked
  // as true.
  boolean checkRevoked = true;
  FirebaseToken decodedToken = FirebaseAuth.getInstance()
      .verifyIdToken(idToken, checkRevoked);
  // Token is valid and not revoked.
  String uid = decodedToken.getUid();
} catch (FirebaseAuthException e) {
  if (e.getAuthErrorCode() == AuthErrorCode.REVOKED_ID_TOKEN) {
    // Token has been revoked. Inform the user to re-authenticate or signOut() the user.
  } else {
    // Token is invalid.
  }
}

파이썬

try:
    # Verify the ID token while checking if the token is revoked by
    # passing check_revoked=True.
    decoded_token = auth.verify_id_token(id_token, check_revoked=True)
    # Token is valid and not revoked.
    uid = decoded_token['uid']
except auth.RevokedIdTokenError:
    # Token revoked, inform the user to reauthenticate or signOut().
    pass
except auth.UserDisabledError:
    # Token belongs to a disabled user record.
    pass
except auth.InvalidIdTokenError:
    # Token is invalid
    pass

가다

client, err := app.Auth(ctx)
if err != nil {
	log.Fatalf("error getting Auth client: %v\n", err)
}
token, err := client.VerifyIDTokenAndCheckRevoked(ctx, idToken)
if err != nil {
	if err.Error() == "ID token has been revoked" {
		// Token is revoked. Inform the user to reauthenticate or signOut() the user.
	} else {
		// Token is invalid
	}
}
log.Printf("Verified ID token: %v\n", token)

씨#

try
{
    // Verify the ID token while checking if the token is revoked by passing checkRevoked
    // as true.
    bool checkRevoked = true;
    var decodedToken = await FirebaseAuth.DefaultInstance.VerifyIdTokenAsync(
        idToken, checkRevoked);
    // Token is valid and not revoked.
    string uid = decodedToken.Uid;
}
catch (FirebaseAuthException ex)
{
    if (ex.AuthErrorCode == AuthErrorCode.RevokedIdToken)
    {
        // Token has been revoked. Inform the user to re-authenticate or signOut() the user.
    }
    else
    {
        // Token is invalid.
    }
}

클라이언트에서 토큰 해지에 응답

Admin SDK를 통해 토큰이 취소된 경우 클라이언트는 취소 사실을 알리고 사용자는 다시 인증해야 하거나 로그아웃됩니다.

function onIdTokenRevocation() {
  // For an email/password user. Prompt the user for the password again.
  let password = prompt('Please provide your password for reauthentication');
  let credential = firebase.auth.EmailAuthProvider.credential(
      firebase.auth().currentUser.email, password);
  firebase.auth().currentUser.reauthenticateWithCredential(credential)
    .then(result => {
      // User successfully reauthenticated. New ID tokens should be valid.
    })
    .catch(error => {
      // An error occurred.
    });
}

고급 보안: IP 주소 제한 적용

토큰 도난을 감지하기 위한 일반적인 보안 메커니즘은 요청 IP 주소 출처를 추적하는 것입니다. 예를 들어 요청이 항상 동일한 IP 주소(호출하는 서버)에서 오는 경우 단일 IP 주소 세션이 시행될 수 있습니다. 또는 사용자의 IP 주소가 갑자기 지리적 위치를 변경한 것을 감지하거나 의심스러운 출처에서 요청을 받은 경우 사용자의 토큰을 취소할 수 있습니다.

IP 주소를 기반으로 보안 검사를 수행하려면 인증된 모든 요청에 ​​대해 ID 토큰을 검사하고 제한된 데이터에 대한 액세스를 허용하기 전에 요청의 IP 주소가 이전의 신뢰할 수 있는 IP 주소와 일치하는지 또는 신뢰할 수 있는 범위 내에 있는지 확인합니다. 예를 들어:

app.post('/getRestrictedData', (req, res) => {
  // Get the ID token passed.
  const idToken = req.body.idToken;
  // Verify the ID token, check if revoked and decode its payload.
  admin.auth().verifyIdToken(idToken, true).then((claims) => {
    // Get the user's previous IP addresses, previously saved.
    return getPreviousUserIpAddresses(claims.sub);
  }).then(previousIpAddresses => {
    // Get the request IP address.
    const requestIpAddress = req.connection.remoteAddress;
    // Check if the request IP address origin is suspicious relative to previous
    // IP addresses. The current request timestamp and the auth_time of the ID
    // token can provide additional signals of abuse especially if the IP address
    // suddenly changed. If there was a sudden location change in a
    // short period of time, then it will give stronger signals of possible abuse.
    if (!isValidIpAddress(previousIpAddresses, requestIpAddress)) {
      // Invalid IP address, take action quickly and revoke all user's refresh tokens.
      revokeUserTokens(claims.uid).then(() => {
        res.status(401).send({error: 'Unauthorized access. Please login again!'});
      }, error => {
        res.status(401).send({error: 'Unauthorized access. Please login again!'});
      });
    } else {
      // Access is valid. Try to return data.
      getData(claims).then(data => {
        res.end(JSON.stringify(data);
      }, error => {
        res.status(500).send({ error: 'Server error!' })
      });
    }
  });
});