封鎖函式可讓你執行自訂程式碼,修改 使用者註冊或登入您的應用程式時。舉例來說 如果使用者不符合特定條件,或無法更新使用者的 再傳回用戶端應用程式。
事前準備
如要使用封鎖函式,您必須將 Firebase 專案升級至 Firebase Authentication with Identity Platform。 若您尚未升級,請先完成升級。
瞭解封鎖函式
您可以為兩個事件註冊封鎖函式:
beforeCreate
:在系統將新使用者儲存至 Firebase Authentication 資料庫,並在將權杖傳回至 用戶端應用程式。beforeSignIn
:在使用者憑證通過驗證後觸發, Firebase Authentication 前,將 ID 符記傳回用戶端應用程式。如果 應用程式採用多重驗證 在使用者驗證雙重驗證後觸發。請注意,建立新的網路時 除了beforeCreate
以外,使用者也會觸發beforeSignIn
。
使用封鎖函式時,請注意下列事項:
您的函式必須在 7 秒內回應。7 秒後 Firebase Authentication 傳回錯誤,且用戶端作業失敗。
200
以外的 HTTP 回應代碼會傳遞至您的用戶端應用程式。確保 您的用戶端程式碼會處理函式可能傳回的任何錯誤。功能適用於專案中的所有使用者,包括 用戶群。 Firebase Authentication 可為您的函式提供使用者相關資訊,包括 因此都能根據情況做出回應。
將其他識別資訊提供者連結至帳戶,都會重新觸發所有已註冊的
beforeSignIn
函式。匿名和自訂驗證不會觸發封鎖函式。
部署封鎖函式
如要在使用者驗證流程中插入自訂程式碼,請部署封鎖功能 函式。部署封鎖函式之後,您的自訂程式碼就必須 成功完成驗證和使用者建立作業。
部署封鎖函式的方式與部署任何函式相同。 (請參閱 Cloud Functions 的「開始使用」頁面) )。簡單來說:
編寫 Cloud Functions 來處理
beforeCreate
事件,beforeSignIn
事件,或同時事件。舉例來說,您可以先新增下列免人工管理函式,
index.js
:const functions = require('firebase-functions/v1'); exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => { // TODO }); exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => { // TODO });
上述範例省略自訂驗證邏輯的實作。詳情請見 以下各節將說明如何導入封鎖函式 常見情況,有具體範例。
使用 Firebase CLI 部署函式:
firebase deploy --only functions
每次更新函式時,您都必須重新部署函式。
取得使用者和背景資訊
beforeSignIn
和 beforeCreate
事件提供 User
和 EventContext
物件,其中含有使用者登入的相關資訊。使用這些值
判斷是否要允許作業繼續執行。
如需 User
物件可用的屬性清單,請參閱
UserRecord
API 參考資料。
EventContext
物件包含下列屬性:
名稱 | 說明 | 範例 |
---|---|---|
locale |
應用程式語言代碼。您可以使用 用戶端 SDK,或在 REST API 中傳遞語言代碼標頭。 | fr 或sv-SE |
ipAddress
| 使用者註冊或登入裝置的 IP 位址 | 114.14.200.1 |
userAgent
| 觸發封鎖函式的使用者代理程式。 | Mozilla/5.0 (X11; Linux x86_64) |
eventId
| 事件的專屬 ID。 | rWsyPtolplG2TBFoOkkgyg |
eventType
|
事件類型。這會提供事件名稱的相關資訊,例如
beforeSignIn 或 beforeCreate ,以及
相關聯的登入方式,例如 Google 或電子郵件/密碼。
|
providers/cloud.auth/eventTypes/user.beforeSignIn:password
|
authType
| 一律為 USER 。 |
USER
|
resource
| Firebase Authentication 專案或用戶群。 |
projects/project-id/tenants/tenant-id
|
timestamp
| 事件觸發時間,格式為 RFC 3339 字串。 | Tue, 23 Jul 2019 21:10:57 GMT
|
additionalUserInfo
| 包含使用者相關資訊的物件。 |
AdditionalUserInfo
|
credential
| 包含使用者憑證相關資訊的物件。 |
AuthCredential
|
封鎖註冊或登入功能
如要禁止註冊或登入嘗試,請在您的HttpsError
函式。例如:
Node.js
throw new functions.auth.HttpsError('permission-denied');
下表列出您可以引發的錯誤及其預設值 錯誤訊息:
名稱 | 程式碼 | 訊息 |
---|---|---|
invalid-argument |
400 |
用戶端指定的引數無效。 |
failed-precondition |
400 |
無法在目前的系統狀態下執行要求。 |
out-of-range |
400 |
用戶端指定的範圍無效。 |
unauthenticated |
401 |
OAuth 權杖遺失、無效或過期。 |
permission-denied |
403 |
用戶端權限不足。 |
not-found |
404 |
找不到您指定的資源。 |
aborted |
409 |
並行衝突,例如「讀取-修改-寫入」衝突。 |
already-exists |
409 |
用戶端嘗試建立的資源已存在。 |
resource-exhausted |
429 |
資源配額用盡或達到頻率限制。 |
cancelled |
499 |
用戶端已取消要求。 |
data-loss |
500 |
發生無法復原的資料遺失或資料毀損情形。 |
unknown |
500 |
發生不明的伺服器錯誤。 |
internal |
500 |
內部伺服器發生錯誤。 |
not-implemented |
501 |
伺服器未執行 API 方法。 |
unavailable |
503 |
服務無法使用。 |
deadline-exceeded |
504 |
已超出要求期限。 |
您也可以指定自訂錯誤訊息:
Node.js
throw new functions.auth.HttpsError('permission-denied', 'Unauthorized request origin!');
以下範例說明如何封鎖不在特定範圍內的使用者 註冊應用程式所需的網域:
Node.js
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
// (If the user is authenticating within a tenant context, the tenant ID can be determined from
// user.tenantId or from context.resource, e.g. 'projects/project-id/tenant/tenant-id-1')
// Only users of a specific domain can sign up.
if (user.email.indexOf('@acme.com') === -1) {
throw new functions.auth.HttpsError('invalid-argument', `Unauthorized email "${user.email}"`);
}
});
無論您使用預設訊息或自訂訊息 Cloud Functions 會納入錯誤,並以 發生內部錯誤。例如:
throw new functions.auth.HttpsError('invalid-argument', `Unauthorized email user@evil.com}`);
您的應用程式應擷取錯誤,並據此處理。例如:
JavaScript
// Blocking functions can also be triggered in a multi-tenant context before user creation.
// firebase.auth().tenantId = 'tenant-id-1';
firebase.auth().createUserWithEmailAndPassword('johndoe@example.com', 'password')
.then((result) => {
result.user.getIdTokenResult()
})
.then((idTokenResult) => {
console.log(idTokenResult.claim.admin);
})
.catch((error) => {
if (error.code !== 'auth/internal-error' && error.message.indexOf('Cloud Function') !== -1) {
// Display error.
} else {
// Registration succeeds.
}
});
修改使用者
除了封鎖註冊或登入嘗試之外,您可以
作業以繼續,但修改儲存在此物件的 User
物件
Firebase Authentication 的資料庫並傳回用戶端。
如要修改使用者,請從包含 欄位。您可以修改下列欄位:
displayName
disabled
emailVerified
photoUrl
customClaims
sessionClaims
(僅限beforeSignIn
)
除了 sessionClaims
以外,所有已修改的欄位都會儲存至
Firebase Authentication 的資料庫,也就是說,該資料庫會包含在回應中
權杖並保留於使用者工作階段之間
以下範例說明如何設定預設顯示名稱:
Node.js
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
return {
// If no display name is provided, set it to "Guest".
displayName: user.displayName || 'Guest';
};
});
如果您同時為 beforeCreate
和 beforeSignIn
註冊事件處理常式,
請注意,beforeSignIn
會在 beforeCreate
之後執行。已更新以下使用者欄位:
「beforeCreate
」會顯示在 beforeSignIn
中。如果您設定的欄位
兩個事件處理常式中的 sessionClaims
,beforeSignIn
中設定的值
會覆寫 beforeCreate
中設定的值。僅適用於 sessionClaims
:
套用至目前工作階段的權杖憑證附加資訊,但不會保存或
這些資料。
舉例來說,如果設定了任何 sessionClaims
,beforeSignIn
會傳回其
提出任何 beforeCreate
著作權聲明,系統就會合併這些著作權聲明。合併後
sessionClaims
鍵與 customClaims
中的鍵相符,即
sessionClaims
將覆寫憑證憑證附加資訊中的 customClaims
。
鍵。不過,過度配置的 customClaims
金鑰仍會保留在
資料庫來處理日後的要求
支援的 OAuth 憑證和資料
您可以將 OAuth 憑證和資料傳遞至多種不同的函式 和識別資訊提供者下表顯示了憑證和資料 支援下列各識別資訊提供者:
識別資訊提供者 | ID 權杖 | 存取權杖 | 到期時間 | 權杖密鑰 | 更新權杖 | 登入要求 |
---|---|---|---|---|---|---|
是 | 是 | 是 | 否 | 是 | 否 | |
否 | 是 | 是 | 否 | 否 | 否 | |
否 | 是 | 否 | 是 | 否 | 否 | |
GitHub | 否 | 是 | 否 | 否 | 否 | 否 |
Microsoft | 是 | 是 | 是 | 否 | 是 | 否 |
否 | 是 | 是 | 否 | 否 | 否 | |
Yahoo | 是 | 是 | 是 | 否 | 是 | 否 |
Apple | 是 | 是 | 是 | 否 | 是 | 否 |
SAML | 否 | 否 | 否 | 否 | 否 | 是 |
OIDC | 是 | 是 | 是 | 否 | 是 | 是 |
重新整理權杖
如要在封鎖函式中使用更新權杖,您必須先選取 勾選 Firebase 控制台「封鎖函式」頁面中的核取方塊。
所有識別資訊提供者登入時,都不會傳回重新整理權杖 登入 OAuth 憑證 例如 ID 權杖或存取權杖在本 相同的用戶端 OAuth 憑證則會傳遞至 函式。
以下各節將說明各個識別資訊提供者類型及其支援的類型 憑證和資料
一般 OIDC 供應商
當使用者透過一般 OIDC 提供者登入時,下列憑證 將會傳遞:
- ID 權杖:選取
id_token
流程時提供。 - 存取權杖:選取程式碼流程時提供。請注意 目前只能透過 REST API 支援流程。
- 「Refresh token」:如有
offline_access
個範圍 已選取
範例:
const provider = new firebase.auth.OAuthProvider('oidc.my-provider');
provider.addScope('offline_access');
firebase.auth().signInWithPopup(provider);
當使用者透過 Google 帳戶登入時,系統會傳送下列憑證:
- ID 權杖
- 存取權杖
- 更新權杖:只有在下列自訂參數符合下列條件時才提供
已要求:
access_type=offline
prompt=consent
(使用者先前已同意但否) 已要求新的範圍
範例:
const provider = new firebase.auth.GoogleAuthProvider();
provider.setCustomParameters({
'access_type': 'offline',
'prompt': 'consent'
});
firebase.auth().signInWithPopup(provider);
進一步瞭解 Google 更新權杖。
當使用者透過 Facebook 登入時,將傳送下列憑證:
GitHub
當使用者透過 GitHub 登入時,系統將傳遞以下憑證:
- 存取權杖:除非撤銷,否則不會過期。
Microsoft
當使用者透過 Microsoft 登入時,將傳送下列憑證:
- ID 權杖
- 存取權杖
- 更新權杖:如果
offline_access
個範圍 已選取
範例:
const provider = new firebase.auth.OAuthProvider('microsoft.com');
provider.addScope('offline_access');
firebase.auth().signInWithPopup(provider);
Yahoo
使用者透過 Yahoo 登入時,系統將傳送下列憑證 不含任何自訂參數或範圍:
- ID 權杖
- 存取權杖
- 重新整理權杖
當使用者透過 LinkedIn 登入時,系統將傳送下列憑證:
- 存取權杖
Apple
當使用者透過 Apple 登入時,系統會傳送下列憑證 不含任何自訂參數或範圍:
- ID 權杖
- 存取權杖
- 重新整理權杖
常見情境
以下範例說明封鎖函式的部分常見用途:
只允許特定網域註冊
以下範例說明如何避免未加入
已向您的應用程式註冊 example.com
個網域:
Node.js
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
if (!user.email || user.email.indexOf('@example.com') === -1) {
throw new functions.auth.HttpsError(
'invalid-argument', `Unauthorized email "${user.email}"`);
}
});
禁止電子郵件未經驗證的使用者註冊
以下範例說明如何防止電子郵件未經驗證的使用者 註冊您的應用程式:
Node.js
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
if (user.email && !user.emailVerified) {
throw new functions.auth.HttpsError(
'invalid-argument', `Unverified email "${user.email}"`);
}
});
要求在註冊後進行電子郵件驗證
以下範例說明如何要求使用者於符合以下條件後驗證電子郵件: 註冊中:
Node.js
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
const locale = context.locale;
if (user.email && !user.emailVerified) {
// Send custom email verification on sign-up.
return admin.auth().generateEmailVerificationLink(user.email).then((link) => {
return sendCustomVerificationEmail(user.email, link, locale);
});
}
});
exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
if (user.email && !user.emailVerified) {
throw new functions.auth.HttpsError(
'invalid-argument', `"${user.email}" needs to be verified before access is granted.`);
}
});
將特定識別資訊提供者的電子郵件視為已驗證
以下範例說明如何處理來自特定身分的使用者電子郵件 已通過驗證的供應商:
Node.js
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
if (user.email && !user.emailVerified && context.eventType.indexOf(':facebook.com') !== -1) {
return {
emailVerified: true,
};
}
});
禁止從特定 IP 位址登入
以下範例說明如何禁止來自特定 IP 位址範圍的登入活動:
Node.js
exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
if (isSuspiciousIpAddress(context.ipAddress)) {
throw new functions.auth.HttpsError(
'permission-denied', 'Unauthorized access!');
}
});
設定自訂與工作階段憑證附加資訊
以下範例說明如何設定自訂憑證和工作階段憑證附加資訊:
Node.js
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
if (context.credential &&
context.credential.providerId === 'saml.my-provider-id') {
return {
// Employee ID does not change so save in persistent claims (stored in
// Auth DB).
customClaims: {
eid: context.credential.claims.employeeid,
},
// Copy role and groups to token claims. These will not be persisted.
sessionClaims: {
role: context.credential.claims.role,
groups: context.credential.claims.groups,
}
}
}
});
追蹤 IP 位址以監控可疑活動
您可以追蹤使用者登入的 IP 位址,防止憑證遭竊。 並與後續要求的 IP 位址進行比較如果請求 看起來很可疑;舉例來說,IP 來自不同的地理區域 區域 — 您可以要求使用者重新登入。
使用工作階段憑證附加資訊,追蹤使用者登入的 IP 位址:
Node.js
exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => { return { sessionClaims: { signInIpAddress: context.ipAddress, }, }; });
使用者嘗試存取需要驗證的資源時 Firebase Authentication,請比對要求中的 IP 位址與所用 IP 登入:
Node.js
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 request IP address const requestIpAddress = req.connection.remoteAddress; // Get sign-in IP address. const signInIpAddress = claims.signInIpAddress; // Check if the request IP address origin is suspicious relative to // the session 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 // geographical change in a short period of time, then it will give // stronger signals of possible abuse. if (!isSuspiciousIpAddressChange(signInIpAddress, requestIpAddress)) { // Suspicious IP address change. Require re-authentication. // You can also revoke all user sessions by calling: // admin.auth().revokeRefreshTokens(claims.sub). 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!' }) }); } }); });
正在過濾使用者的相片
以下範例說明如何清理使用者的個人資料相片:
Node.js
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
if (user.photoURL) {
return isPhotoAppropriate(user.photoURL)
.then((status) => {
if (!status) {
// Sanitize inappropriate photos by replacing them with guest photos.
// Users could also be blocked from sign-up, disabled, etc.
return {
photoUrl: PLACEHOLDER_GUEST_PHOTO_URL,
};
}
});
});
如要進一步瞭解如何偵測圖片及進行淨化處理,請參閱 Cloud Vision 說明文件。
存取使用者的識別資訊提供者 OAuth 憑證
下例示範如何取得使用者更新權杖 並用來呼叫 Google Calendar API。 儲存更新權杖以供離線存取。
Node.js
const {OAuth2Client} = require('google-auth-library');
const {google} = require('googleapis');
// ...
// Initialize Google OAuth client.
const keys = require('./oauth2.keys.json');
const oAuth2Client = new OAuth2Client(
keys.web.client_id,
keys.web.client_secret
);
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
if (context.credential &&
context.credential.providerId === 'google.com') {
// Store the refresh token for later offline use.
// These will only be returned if refresh tokens credentials are included
// (enabled by Cloud console).
return saveUserRefreshToken(
user.uid,
context.credential.refreshToken,
'google.com'
)
.then(() => {
// Blocking the function is not required. The function can resolve while
// this operation continues to run in the background.
return new Promise((resolve, reject) => {
// For this operation to succeed, the appropriate OAuth scope should be requested
// on sign in with Google, client-side. In this case:
// https://www.googleapis.com/auth/calendar
// You can check granted_scopes from within:
// context.additionalUserInfo.profile.granted_scopes (space joined list of scopes).
// Set access token/refresh token.
oAuth2Client.setCredentials({
access_token: context.credential.accessToken,
refresh_token: context.credential.refreshToken,
});
const calendar = google.calendar('v3');
// Setup Onboarding event on user's calendar.
const event = {/** ... */};
calendar.events.insert({
auth: oauth2client,
calendarId: 'primary',
resource: event,
}, (err, event) => {
// Do not fail. This is a best effort approach.
resolve();
});
});
})
}
});
覆寫使用者作業的 reCAPTCHA Enterprise 判定結果
以下範例說明如何針對支援的使用者流程覆寫 reCAPTCHA Enterprise 判定結果。
如要進一步瞭解如何整合 reCAPTCHA Enterprise 與 Firebase 驗證,請參閱「啟用 reCAPTCHA Enterprise」一文。
透過封鎖函式,您可以根據自訂因素允許或封鎖流程,進而覆寫 reCAPTCHA Enterprise 提供的結果。
Node.js
const {
auth,
} = require("firebase-functions/v1");
exports.checkrecaptchaV1 = auth.user().beforeSignIn((userRecord, context) => {
// Allow users with a specific email domain to sign in regardless of their recaptcha score.
if (userRecord.email && userRecord.email.indexOf('@acme.com') === -1) {
return {
recaptchaActionOverride: 'ALLOW',
};
}
// Allow users to sign in with recaptcha score greater than 0.5
if (context.additionalUserInfo.recaptchaScore > 0.5) {
return {
recaptchaActionOverride: 'ALLOW',
};
}
// Block all others.
return {
recaptchaActionOverride: 'BLOCK',
};
});