firebase auth

本文最初发布在Auth0.com博客上 ,并经许可在此处重新发布。

在这个分为两部分的系列教程中,我们将学习如何构建一个使用Auth0身份验证保护Node后端和Angular前端安全的应用程序。 我们的服务器和应用程序还将使用自定义令牌对Firebase Cloud Firestore数据库进行身份验证,以便用户在使用Auth0登录后可以安全方式留下实时评论。

可以在angular-firebase GitHub存储库中找到Angular应用程序代码,在firebase-auth0-nodeserver存储库中找到Node API。

使用Auth0对Firebase和Angular进行身份验证:第1部分

本教程的第1部分将介绍:

  1. Firebase和Auth0
  2. 我们将建立什么
  3. 角度CLI
  4. Auth0客户端和API
  5. 具有服务帐户的Firebase项目
  6. 节点API
  7. 设置Angular应用
  8. Angular应用架构
  9. 实施共享模块
  10. 实现路由和延迟加载模块
  11. 加载和错误组件
  12. 验证逻辑
  13. 核心逻辑
  14. 下一步

Firebase和Auth0

Firebase是一个移动和Web应用程序开发平台。 Firebase于2014年被Google收购,并将继续在Google的领导下进行开发。 Firebase提供了NoSQL数据库( RTDB或Realtime Database and Cloud Firestore,在撰写本文时为beta版),该 数据库托管在云中,并使用Web套接字进行连接以向应用程序提供实时功能。

Auth0是基于云的平台,提供身份验证和授权即服务。 作为身份验证提供程序,Auth0使开发人员可以轻松地实现和自定义其应用程序的登录和授权安全性。

选择Auth0 + Firebase身份验证

如果您已经熟悉Firebase的产品,您可能会问:为什么我们要在Firebase中使用自定义令牌实现Auth0,而不是坚持使用Firebase的内置身份验证 ?

首先,在这里有一个重要的区别。 使用Auth0保护Firebase并不意味着您使用Firebase身份验证。 Firebase具有自定义身份验证方法 ,允许开发人员将其首选身份解决方案 Firebase身份验证集成。 这种方法使开发人员能够实施Firebase身份验证,以便它与专有系统或其他身份验证提供程序无缝运行。

我们可能想将Auth0与Firebase身份验证集成的潜在原因有很多。 另外,在某些情况下,仅使用基本的Firebase身份验证就足够了。 让我们来探索。

如果您满足以下条件,则可以单独使用Firebase的内置身份验证

  • 只想对Firebase RTDB或Firestore进行身份验证,而无需对其他后端进行身份验证
  • 只需要少量的登录选项,不需要企业标识提供程序,与您自己的用户存储数据库集成等。
  • 不需要大量的用户管理,配置文件扩充等,并且可以通过API严格地管理用户
  • 无需自定义身份验证流程
  • 无需遵守有关用户数据存储的合规性法规。

如果您执行以下操作,则应考虑将Auth0与自定义Firebase令牌结合使用:

  • 已经实现了Auth0,并希望向您的应用添加实时功能
  • 需要轻松使用发行的令牌来保护 Firebase 提供的后端
  • 需要整合社交身份提供者 ,而不仅仅是Google,Facebook,Twitter和GitHub
  • 需要集成企业身份提供程序 ,例如Active Directory,LDAP,ADFS,SAMLP等。
  • 需要定制的身份验证流程
  • 需要通过API 易于管理的仪表板进行强大的用户管理
  • 希望能够动态丰富用户个人资料
  • 想要可自定义的无密码登录 , 多因素身份验证 , 违反密码安全性 , 异常检测等功能。
  • 必须遵守HIPAA,GDPR,SOC2等合规性法规 。

本质上,如果您有一个非常简单的应用程序具有基本的身份验证需求,并且仅使用Firebase数据库,则Firebase的基本身份验证提供程序就足够了。 但是,如果您还需要更多, Firebase提供了一种将其服务其他身份验证解决方案一起使用的好方法 。 这是许多开发人员都将面对的更为现实的情况,因此我们将在此处详细探讨。

我们将建立什么

我们将构建一个使用Auth0保护的Node.js API,该API会铸造自定义Firebase令牌,并返回十种不同犬种的数据。

我们还将构建一个名为“ Popular Dogs”的Angular前端应用程序,该应用程序显示有关2016年十种最受欢迎​​的狗的信息,该信息由美国养犬俱乐部(AKC)按公众受欢迎程度排名。 我们的应用将通过Auth0保护,调用Node API来获取狗数据,并调用API获取Firebase令牌以授权用户使用Cloud Firestore实时添加和删除评论。 该应用程序将使用共享模块以及实现延迟加载。

要实施该应用程序,您将需要以下内容:

  • 角度CLI
  • 具有客户端和API配置的免费Auth0帐户
  • 具有服务帐户的免费Firebase项目

让我们开始吧!

角度CLI

确保在本地计算机上安装了带有NPM的Node.js。 运行以下命令以全局安装Angular CLI :

$ npm install -g @angular/cli@latest

我们将使用CLI生成Angular应用及其几乎所有架构。

Auth0客户端和API

免费学习PHP!

全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。

原价$ 11.95 您的完全免费

免费获得这本书

您需要一个Auth0帐户来管理身份验证。 您可以在此处注册一个免费帐户 。

接下来,设置Auth0客户端应用程序和API,以便Auth0可以与Angular应用程序和Node API交互。

设置Auth0客户端

  1. 转到您的Auth0信息中心 ,然后点击创建新客户端按钮。
  2. 为您的新应用命名(例如Angular Firebase ),然后选择单页Web应用
  3. 在新的Auth0客户端应用的设置中,将http://localhost:4200/callback允许的回调URL中
  4. 启用使用Auth0(而不是IdP)进行单点登录的切换。
  5. 在“ 设置”部分的底部,单击“显示高级设置”。 选择OAuth选项卡,并验证JsonWebToken签名算法是否设置为“ RS256”。
  6. 如果需要,您可以建立一些社交关系 。 然后,可以在“ 连接”选项卡下的“ 客户端”选项中为您的应用启用它们。 上面的屏幕快照中显示的示例使用用户名/密码数据库,Facebook,Google和Twitter。

注意:对于生产,请确保您设置了自己的社交密钥,并且不要将社交连接设置为使用Auth0开发密钥。

设置Auth0 API

  1. 转到Auth0信息中心中的API ,然后单击“创建API”按钮。 输入API的名称,例如Firebase Dogs API 。 将标识符设置为您的API端点URL。 在本教程中,我们的API标识符为http://localhost:1337/签名算法应为“ RS256”。
  2. 您可以在新API设置的“ 快速入门”选项卡下查阅Node.js示例。 在接下来的步骤中,我们将使用Express , express-jwt和jwks-rsa以这种方式实现Node API。

现在,我们准备在Angular客户端和Node后端API上实现Auth0身份验证。

具有服务帐户的Firebase项目

接下来,您将需要一个免费的Firebase项目。

创建Firebase项目

  1. 转到Firebase控制台,然后使用您的Google帐户登录。
  2. 单击添加项目
  3. 在弹出的对话框中,为您的项目命名(例如Angular Firebase Auth0 )。 将根据您选择的名称生成一个项目ID。 然后,您可以选择您的国家/地区。
  4. 单击创建项目按钮。

生成管理员SDK密钥

为了创建自定义的Firebase令牌 ,您需要访问Firebase Admin SDK 。 要获得访问权限,您必须在新的Firebase项目中创建一个服务帐户。

单击Firebase控制台侧栏中项目概述旁边的齿轮图标,然后从出现的菜单中选择项目设置

在设置视图中,单击“ 服务帐户”选项卡。 将显示Firebase Admin SDK UI,其中显示配置代码段。 默认情况下,选择Node.js。 这是我们想要的技术,我们将在Node API中实现它。 单击生成新私钥按钮。

将出现一个对话框,警告您秘密存储私钥。 我们将永远不要将这个密钥签入公共存储库。 单击Generate Key按钮,将密钥下载为.json文件。 我们将很快将此文件添加到我们的Node API。

节点API

可以在firebase-auth0-nodeserver GitHub存储库中找到本教程的完整Node.js API。 让我们学习如何构建此API。

节点API文件结构

我们将要设置以下文件结构:

firebase-auth0-nodeserver/
|--firebase/
|--.gitignore
|--<your-firebase-admin-sdk-key>.json
|--.gitignore
|--config.js
|--dogs.json
|--package.json
|--routes.js
|--server.js

您可以使用命令行生成必要的文件夹和文件,如下所示:

$ mkdir firebase-auth0-nodeserver
$ cd firebase-auth0-nodeserver
$ mkdir firebase
$ touch firebase/.gitignore
$ touch .gitignore
$ touch config.js
$ touch dogs.json
$ touch package.json
$ touch routes.js
$ touch server.js

Firebase Admin SDK密钥和Git忽略

现在,将您先前下载的Firebase Admin SDK .json密钥文件移动到firebase文件夹中。 我们会确保文件夹已签入,但绝对不会使用firebase/.gitignore将其内容推送到存储库中,如下所示:

# firebase/.gitignore
*
*/
!.gitignore

.gitignore配置可确保Git忽略firebase目录中的所有文件和文件夹, 除了 .gitignore文件本身。 这使我们可以提交(基本上)空的文件夹。 我们的.json Firebase Admin SDK密钥可以存在于此文件夹中,我们不必担心通过filename忽略它。

注意:如果我们将项目拉到多台计算机上并且生成了不同的密钥(具有不同的文件名),这将特别有用。

接下来,我们为根目录的.gitignore添加代码:

# .gitignore
config.js
node_modules

狗的JSON数据

接下来,我们将添加十个犬种的数据。 为简便起见,您可以简单地将此数据复制并粘贴到dogs.json文件中。

依存关系

让我们像这样添加我们的package.json文件:

{
"name": "firebase-auth0-nodeserver",
"version": "0.1.0",
"description": "Node.js server that authenticates with an Auth0 access token and returns a Firebase auth token.",
"repository": "https://github.com/auth0-blog/firebase-auth0-nodeserver",
"main": "server.js",
"scripts": {
"start": "node server"
},
"author": "Auth0",
"license": "MIT",
"dependencies": {},
"devDependencies": {}
}

我们将使用命令行安装依赖项,最新版本将自动保存到package.json文件中:

$ npm install --save body-parser cors express express-jwt jwks-rsa firebase-admin

我们需要body-parsercorsexpress来提供我们的API端点。 身份验证将依赖于express-jwtjwks-rsa ,而Firebase令牌铸造是通过firebase-admin SDK(我们将使用生成的密钥进行访问)实现的。

组态

config.js文件中,添加以下代码,并将占位符值替换为您自己的设置:

// config.js
module.exports = {
AUTH0_DOMAIN: '<Auth0 Domain>', // e.g., you.auth0.com
AUTH0_API_AUDIENCE: '<Auth0 API Audience>', // e.g., http://localhost:1337/
FIREBASE_KEY: './firebase/<Firebase JSON>', // e.g., your-project-firebase-adminsdk-xxxxx-xxxxxxxxxx.json
FIREBASE_DB: '<Firebase Database URL>' // e.g., https://your-project.firebaseio.com
};

服务器

有了我们的数据,配置和依赖关系,我们现在就可以实现我们的Node服务器。 打开server.js文件并添加:

// server.js
// Modules
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
// App
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cors());
// Set port
const port = process.env.PORT || '1337';
app.set('port', port);
// Routes
require('./routes')(app);
// Server
app.listen(port, () => console.log(`Server running on localhost:${port}`));

这将使用http://localhost:1337/ Express启动我们的Node服务器。

注意:请注意,这是我们在Auth0中设置的API标识符。

API路由

接下来打开routes.js文件。 这是我们定义API端点,保护它们和铸造自定义Firebase令牌的地方。 添加以下代码:

// routes.js
// Dependencies
const jwt = require('express-jwt');
const jwks = require('jwks-rsa');
const firebaseAdmin = require('firebase-admin');
// Config
const config = require('./config');
module.exports = function(app) {
// Auth0 athentication middleware
const jwtCheck = jwt({
secret: jwks.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `https://${config.AUTH0_DOMAIN}/.well-known/jwks.json`
}),
audience: config.AUTH0_API_AUDIENCE,
issuer: `https://${config.AUTH0_DOMAIN}/`,
algorithm: 'RS256'
});
// Initialize Firebase Admin with service account
const serviceAccount = require(config.FIREBASE_KEY);
firebaseAdmin.initializeApp({
credential: firebaseAdmin.credential.cert(serviceAccount),
databaseURL: config.FIREBASE_DB
});
// GET object containing Firebase custom token
app.get('/auth/firebase', jwtCheck, (req, res) => {
// Create UID from authenticated Auth0 user
const uid = req.user.sub;
// Mint token using Firebase Admin SDK
firebaseAdmin.auth().createCustomToken(uid)
.then(customToken =>
// Response must be an object or Firebase errors
res.json({firebaseToken: customToken})
)
.catch(err =>
res.status(500).send({
message: 'Something went wrong acquiring a Firebase token.',
error: err
})
);
});
// Set up dogs JSON data for API
const dogs = require('./dogs.json');
const getDogsBasic = () => {
const dogsBasicArr = dogs.map(dog => {
return {
rank: dog.rank,
breed: dog.breed,
image: dog.image
}
});
return dogsBasicArr;
}
// GET dogs (public)
app.get('/api/dogs', (req, res) => {
res.send(getDogsBasic());
});
// GET dog details by rank (private)
app.get('/api/dog/:rank', jwtCheck, (req, res) => {
const rank = req.params.rank * 1;
const thisDog = dogs.find(dog => dog.rank === rank);
res.send(thisDog);
});
};

在较高级别,我们的路由文件执行以下操作:

  • 设置身份验证检查,以确保只有登录用户才能使用jwtCheck中间件访问路由
  • 使用从Firebase项目服务帐户生成的私钥初始化Firebase Admin SDK。
  • 提供返回自定义Firebase令牌的安全GET端点
  • 提供一个公共GET *端点,该端点返回dogs数据的简短版本
  • 提供一个安全的GET *端点,该端点返回按等级要求的特定狗的详细数据。

*端点使用相同基本数据集的变体来模拟更复杂的API。

您可以阅读代码注释以获取更多详细信息。

提供API

您可以通过运行以下命令来提供Node API:

$ node server

然后,该API将在http:// localhost:1337可用。

注意:如果尝试在浏览器中访问安全路由,则应收到401 Unauthorized错误。

这就是我们的服务器! 保持API保持运行状态,以便Angular应用可以访问它,我们将在下一步中对其进行设置。

设置Angular应用

现在是时候创建我们的Angular应用程序并设置一些其他依赖项了。

创建新的Angular应用

您应该早先已经安装了Angular CLI 。 现在,我们可以使用CLI生成我们的项目及其架构。 要创建一个新应用,请选择一个包含文件夹,然后运行以下命令:

$ ng new angular-firebase --routing --skip-tests

--routing标志生成具有路由模块的应用程序,而--skip-tests生成不具有.spec.ts文件的根组件。

注意:为简便起见,本文将不涉及测试。 如果您想了解有关在Angular中进行测试的更多信息,请查看教程的结论以获取更多资源。

安装前端依赖项

现在,让我们安装前端依赖项:

$ cd angular-firebase
$ npm install --save auth0-js@latest firebase@latest angularfire2@latest

我们将需要auth0-js库在Angular应用中实现Auth0身份验证。 我们还需要在firebase JS SDK和angularfire2角火力地堡库来实现我们的火力地堡的实时评论。

添加Bootstrap CSS

为了简化样式,我们将Bootstrap CSS CDN链接添加到index.html文件的<head> ,如下所示:

<!-- src/index.html -->
...
<head>
...
<title>Top 10 Dogs</title>
...
<link
rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous">
</head>
...

服务角度应用

您可以使用以下命令为Angular应用提供服务:

$ ng serve

该应用程序将在浏览器中的http:// localhost:4200上运行 。

Angular应用架构

我们将使用Angular CLI为我们的应用程序预先生成完整的架构。 这样,我们可以在实现逻辑和模板之前确保模块正常运行。

我们的应用将使用延迟加载模块化方法 。 本教程中的示例应用程序很小,但是我们希望以可扩展的,真实的方式构建它。

根模块

使用ng new命令生成Angular应用时,已经创建了根模块。 根模块位于src/app/app.module.ts 。 我们在Angular应用程序中生成的任何未指定其他模块子目录的组件都将自动导入并在我们的根模块中声明。

现在,使用CLI生成一个组件:

# create CallbackComponent:
$ ng g component callback --is --it --flat --no-spec

该命令由以下内容组成:

  • ng g component :生成具有以下内容的callback组件文件:
  • --is内联样式
  • --it内联模板
  • --flat不包含文件夹
  • --no-spec.spec测试文件

用户登录到我们的应用程序后,我们将使用回调组件来处理重定向。 这是一个非常简单的组件。

注意: ggenerate的快捷方式。 我们还可以使用c作为component的快捷方式,使此命令成为ng gc 但是,为了清楚起见,本教程将不对生成的文件类型使用快捷方式。

核心模块架构

接下来,我们将创建CoreModule及其组件和服务。 这是一个共享模块。 在Angular项目文件夹的根目录中,运行以下CLI命令。 请确保您运行ng g module core第一个命令,就像这样:

# create Core module:
$ ng g module core
# create API service with no .spec file:
$ ng g service core/api --no-spec
# create HeaderComponent with inline styles, no .spec file, and export in module:
$ ng g component core/header --is --no-spec --export=true
# create LoadingComponent with inline styles, inline template, no folder, no .spec file, and export in module:
$ ng g component core/loading --is --it --flat --no-spec --export=true
# create ErrorComponent with inline styles, inline template, no folder, no .spec file, and export in module:
$ ng g component core/error --is --it --flat --no-spec --export=true
# create Dog type interface:
$ ng g interface core/dog
# create DogDetail type interface:
$ ng g interface core/dog-detail

首先创建模块可确保在该模块的文件夹中创建的组件随后将被导入并自动在该父模块(而不是应用程序的根模块)中声明。

注意:如果要在另一个模块中使用共享模块的组件,则需要export组件并声明它们。 我们可以使用--export=true标志通过CLI自动执行此操作。

这是我们的应用程序需要访问的共享核心服务,组件和模型的基本架构。

身份验证模块架构

接下来,我们将创建AuthModule 。 执行以下CLI命令(同样,请确保首先生成模块):

# create Auth module:
$ ng g module auth
# create AuthService with no .spec file:
$ ng g service auth/auth --no-spec
# create Auth route guard with no .spec file:
$ ng g guard auth/auth --no-spec

我们的Auth模块提供了管理身份验证所需的服务和路由防护,但没有任何组件。 这也是一个共享模块。

狗模块架构

我们的应用程序的主页将由DogsModule提供。 根据AKC的排名,这将是2016年十只最受欢迎的狗的名单。 使用以下CLI命令来为该延迟加载的页面模块生成结构:

# create Dogs module:
$ ng g module dogs
# create DogsComponent with inline styles and no .spec file:
$ ng g component dogs/dogs --is --no-spec

狗模块架构

我们的应用程序还将为Dogs组件中列出的每条狗提供详细页面,以便用户可以了解有关每种品种的更多信息。 使用以下CLI命令来为延迟加载的DogModule生成结构:

# create Dog module:
$ ng g module dog
# create DogComponent with inline styles and no .spec file:
$ ng g component dog/dog --is --no-spec

注释模块架构

最后,我们需要实现Firebase实时注释所需的架构。 使用下面的CLI命令生成的结构CommentsModule

# create Comments module:
$ ng g module comments
# create Comment model class:
$ ng g class comments/comment
# create CommentsComponent with no .spec file:
$ ng g component comments/comments --no-spec --export=true
# create CommentFormComponent with inline styles and no .spec file:
$ ng g component comments/comments/comment-form --is --no-spec

环境配置

让我们将Auth0和Firebase的配置信息添加到Angular前端。 打开environment.ts文件并添加:

// src/environments/environment.ts
const FB_PROJECT_ID = '<FIREBASE_PROJECT_ID>';
export const environment = {
production: false,
auth: {
clientId: '<AUTH0_CLIENT_ID>',
clientDomain: '<AUTH0_DOMAIN>', // e.g., you.auth0.com
audience: '<AUTH0_API_AUDIENCE>', // e.g., http://localhost:1337/
redirect: 'http://localhost:4200/callback',
scope: 'openid profile email'
},
firebase: {
apiKey: '<FIREBASE_API_KEY>',
authDomain: `${FB_PROJECT_ID}.firebaseapp.com`,
databaseURL: `https://${FB_PROJECT_ID}.firebaseio.com`,
projectId: FB_PROJECT_ID,
storageBucket: `${FB_PROJECT_ID}.appspot.com`,
messagingSenderId: '<FIREBASE_MESSAGING_SENDER_ID>'
},
apiRoot: '<API URL>' // e.g., http://localhost:1337/ (DO include trailing slash)
};

用适当的Auth0,Firebase和API信息替换<angle brackets>占位符。

您可以在您为本教程创建的客户端和API的设置的Auth0仪表板中找到Auth0配置。

单击标有“ 将Firebase添加到您的Web应用程序”的大图标后,您可以在Firebase控制台项目概述中找到Firebase配置,如下所示:

添加加载图片

开始在Angular应用中实现功能之前,我们要做的最后一件事是添加加载图像。 创建以下文件夹: src/assets/images

然后将此加载的SVG图像保存到该文件夹​​中:

实施共享模块

让我们设置我们的模块。 我们将在根AppModule导入共享模块( CoreModuleAuthModule )。

核心模块

首先,我们将实现我们的CoreModule 。 打开core.module.ts文件并更新为以下代码:

// src/app/core/core.module.ts
import { NgModule, ModuleWithProviders } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { Title } from '@angular/platform-browser';
import { DatePipe } from '@angular/common';
import { HeaderComponent } from './header/header.component';
import { ApiService } from './api.service';
import { LoadingComponent } from './loading.component';
import { ErrorComponent } from './error.component';
@NgModule({
imports: [
CommonModule,
RouterModule,
HttpClientModule, // AuthModule is a sibling and can use this without us exporting it
FormsModule
],
declarations: [
HeaderComponent,
LoadingComponent,
ErrorComponent
],
exports: [
FormsModule, // Export FormsModule so CommentsModule can use it
HeaderComponent,
LoadingComponent,
ErrorComponent
]
})
export class CoreModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: CoreModule,
providers: [
Title,
DatePipe,
ApiService
]
};
}
}

由于这是一个共享模块,因此我们将导入在整个应用程序中需要访问的其他模块,服务和组件。

注意: CommonModule导入到 不是根模块的所有模块中

在我们的imports数组中,我们将在CoreModule添加服务或组件可能需要的任何模块,或者将这些模块提供给应用程序中的其他模块。 CLI应该已经自动将所有生成的组件添加到了declarations数组。 exports数组应包含我们要提供给其他模块的任何模块或组件。

请注意,我们已从@angular/core导入ModuleWithProviders 。 使用此模块,我们可以创建一个forRoot()方法,当导入CoreModule时,可以在根app.module.ts中的导入时调用该方法。 这样,我们可以确保添加到由forRoot()方法返回的providers数组中的所有服务在我们的应用程序中保持单例 。 这样,如果我们应用中的其他模块也需要导入CoreModule ,我们可以避免意外的多个实例。

验证模块

接下来让我们添加一些代码,我们AuthModuleauth.module.ts文件:

// src/app/auth/auth.module.ts
import { NgModule, ModuleWithProviders } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AuthService } from './auth.service';
import { AuthGuard } from './auth.guard';
import { AngularFireAuthModule } from 'angularfire2/auth';
@NgModule({
imports: [
CommonModule,
AngularFireAuthModule
]
})
export class AuthModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: AuthModule,
providers: [
AuthService,
AuthGuard
]
};
}
}

我们将导入ModuleWithProviders以实现与CoreModuleforRoot()方法。 然后,我们将导入AuthServiceAuthGuard 。 我们还需要从angularfire2/auth导入AngularFireAuthModule ,以便我们可以在AuthService保护Firebase连接。 然后,应在forRoot()方法的providers数组中返回服务和防护。

评论模块

打开comments.module.ts文件以实现CommentsModule如下所示:

// src/app/comments/comments.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CoreModule } from '../core/core.module';
import { environment } from './../../environments/environment';
import { AngularFireModule } from 'angularfire2';
import { AngularFirestoreModule } from 'angularfire2/firestore';
import { CommentsComponent } from './comments/comments.component';
import { CommentFormComponent } from './comments/comment-form/comment-form.component';
@NgModule({
imports: [
CommonModule,
CoreModule, // Access FormsModule, Loading, and Error components
AngularFireModule.initializeApp(environment.firebase),
AngularFirestoreModule
],
declarations: [
CommentsComponent,
CommentFormComponent
],
exports: [
CommentsComponent
]
})
export class CommentsModule { }

我们需要导入CoreModule以便可以利用其导出的FormsModuleLoadingComponentErrorComponent 。 我们还需要从environment.ts文件访问我们的配置。 评论使用火力地堡的云数据库的FireStore,让我们导入AngularFireModuleAngularFirestoreModule以及我们的两个组成部分: CommentsComponentCommentFormComponent

当我们将AngularFireModule添加到@NgModule的imports数组时,我们将调用其initializeApp()方法,并传入Firebase配置。 我们的两个组件都应该已经在declarations数组中,并且CommentsComponent应该已经添加到了exports数组中,以便其他模块中的其他组件可以使用它。

注意:我们不需要导出CommentsFormComponent因为它是CommentsComponent的子级。

CommentsModule不提供任何服务,因此无需实现forRoot()方法。

应用模块

现在,我们的CoreModuleAuthModuleCommentsModule已经实现,我们需要导入我们的根模块中,上述AppModule地处app.module.ts文件:

// src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { CoreModule } from './core/core.module';
import { AuthModule } from './auth/auth.module';
import { CommentsModule } from './comments/comments.module';
import { AppComponent } from './app.component';
import { CallbackComponent } from './callback.component';
@NgModule({
declarations: [
AppComponent,
CallbackComponent
],
imports: [
BrowserModule,
AppRoutingModule,
CoreModule.forRoot(),
AuthModule.forRoot(),
CommentsModule
],
bootstrap: [AppComponent]
})
export class AppModule { }

CLI已自动添加了AppComponentCallbackComponent 。 当我们将CoreModuleAuthModule添加到imports数组时,我们将调用forRoot()方法以确保没有为其服务创建额外的实例。 CommentsModule不提供任何服务,因此该模块无需担心。

实现路由和延迟加载模块

我们有两个需要路由的模块: DogsModule用于狗的主要列表)和DogModule ,其中包含显示犬种详细信息页面的组件。

应用程序路由

首先,让我们实现应用程序的路由。 打开app-routing.module.ts文件并添加以下代码:

// src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { CallbackComponent } from './callback.component';
import { AuthGuard } from './auth/auth.guard';
const routes: Routes = [
{
path: '',
loadChildren: './dogs/dogs.module#DogsModule',
pathMatch: 'full'
},
{
path: 'dog',
loadChildren: './dog/dog.module#DogModule',
canActivate: [
AuthGuard
]
},
{
path: 'callback',
component: CallbackComponent
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

我们将导入CallbackComponentAuthGuard 。 其余路由将是对模块的字符串引用 ,而不是使用loadChildren属性导入的组件。

我们将设置默认的''路径来从DogsModule加载路由DogsModule ,而'dog'路径来从DogModule加载路由DogModule'dog'路径也应受到AuthGuard保护,我们使用canActivate属性对其进行了声明。 如果我们需要不止一个,这可以容纳一系列的路由卫士。 最后, 'callback'路由应仅指向CallbackComponent

狗模块

让我们向dogs.module.ts文件添加一些代码:

// src/app/dogs/dogs.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Routes, RouterModule } from '@angular/router';
import { CoreModule } from '../core/core.module';
import { CommentsModule } from '../comments/comments.module';
import { DogsComponent } from './dogs/dogs.component';
const DOGS_ROUTES: Routes = [
{
path: '',
component: DogsComponent
}
];
@NgModule({
imports: [
CommonModule,
CoreModule,
RouterModule.forChild(DOGS_ROUTES),
CommentsModule
],
declarations: [
DogsComponent
]
})
export class DogsModule { }

除了CoreModuleCommentsModule外,我们RouterModule导入RoutesRouterModule (注释将显示在主要的dogs列表页面上)。

这个模块有一个子路由,因此我们将创建一个常量,该常量包含一个数组来保存我们的路由对象。 独生子女的路线,我们需要继承''从路径app-routing.module.ts ,所以它的路径也应该是'' 。 它将加载DogsComponent 。 在imports数组中,我们将DOGS_ROUTES常量传递给RouterModuleforChild()方法。

狗模块

DogModule的工作方式与同样DogsModule以上。 打开dog.module.ts并添加以下内容:

// src/app/dog/dog.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Routes, RouterModule } from '@angular/router';
import { CoreModule } from '../core/core.module';
import { DogComponent } from './dog/dog.component';
const DOG_ROUTES: Routes = [
{
path: ':rank',
component: DogComponent
}
];
@NgModule({
imports: [
CommonModule,
CoreModule,
RouterModule.forChild(DOG_ROUTES)
],
declarations: [
DogComponent
]
})
export class DogModule { }

该模块与DogsModule之间的DogsModule是我们的DOG_ROUTES的路径为:rank 。 这样,任何特定狗的详细信息的路由都会作为URL段传递,该URL段与我们在十大犬种列表中的犬排名相匹配,如下所示:

http://localhost:4200/dog/3

另一个区别是,我们将不会导入CommentsModule 。 但是,如果需要,我们将来可以在狗的详细信息中添加评论。

我们的应用程序的架构和路由现已完成! 该应用程序应成功编译并显示在浏览器中,并且延迟加载功能可以正确加载共享代码和请求的特定路由的代码。

现在,我们准备实现应用程序的逻辑。

加载和错误组件

加载和错误组件是基本的核心UI元素,可以在我们的应用程序中的许多不同位置使用。 现在设置它们。

加载组件

LoadingComponent应该只显示一个加载图像。 (回想一下,我们已经保存一个,当我们建立我们的应用程序的体系结构)。但是,它应该是能够显示图像大和中心, 小和内联。

打开loading.component.ts文件并添加:

// src/app/core/loading.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-loading',
template: `
<div [ngClass]="{'inline': inline, 'text-center': !inline, 'py-2': !inline }">
<img src="/assets/images/loading.svg">
</div>
`,
styles: [`
.inline {
display: inline-block;
}
img {
height: 80px;
width: 80px;
}
.inline img {
height: 24px;
width: 24px;
}
`]
})
export class LoadingComponent {
@Input() inline: boolean;
}

使用@Input()装饰器 ,我们可以将信息从其父级传递到组件中,告诉它是否应该内联显示该组件。 我们将在模板中使用NgClass指令 ( [ngClass] )有条件地为我们想要的显示添加适当的样式。 在另一个模板中显示该组件将如下所示:

<!-- Large, full width, centered: -->
<app-loading></app-loading>
<!-- Inline: -->
<app-loading inline="true"></app-loading>

错误成分

接下来,让我们快速实现我们的ErrorComponent 。 如果显示该组件,将显示一条简单的错误消息。 打开error.component.ts文件并添加:

// src/app/core/error.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-error',
template: `
<p class="alert alert-danger">
<strong>Error:</strong> There was an error retrieving data.
</p>
`
})
export class ErrorComponent {
}

验证逻辑

现在,让我们实现使AuthModule的功能正常工作所需的代码。 为了在CoreModule构建标头,我们将需要身份验证服务,因此从这里开始是有意义的。 我们已经安装了必要的依赖项(Auth0和FirebaseAuth),所以让我们开始吧。

认证服务

在编写任何代码之前,我们将确定该服务的要求。 我们要:

  • 创建一个login()方法,该方法将允许用户使用Auth0进行身份验证
  • 如果提示用户通过尝试访问受保护的路由来登录,请确保在成功通过身份验证后将其重定向到该路由
  • 获取用户的个人资料信息并设置他们的会话
  • 建立让应用知道用户是否已登录的方法
  • 通过Auth0访问令牌的授权,从API请求Firebase自定义令牌
  • 如果成功获取Firebase令牌,请使用返回的令牌登录Firebase,并为应用建立一种方法来了解用户是否已登录Firebase
  • Firebase铸造的自定义令牌会在一小时后过期,因此我们应该设置一种方法来自动续订过期的令牌
  • 创建一个logout()方法以清除会话并logout() Firebase。

打开我们之前生成的auth.service.ts文件。

为了简化教程,请在GitHub repo的auth.service.ts文件中查看完整代码。

发生了很多事情,所以让我们逐步进行一下。

首先,与往常一样,我们将导入依赖项。 这包括我们environment配置之前设置为广大Auth0,火力地堡和API设置,以及auth0firebase库, AngularFireAuthHttpClient调用API来获取自定义火力地堡的令牌,以及必要的RxJS进口。

您可以参考代码注释,以获取有关AuthService类的私有和公共成员的AuthService

接下来是构造函数,在该函数中,我们可以在类中使用RouterAngularFireAuthHttpClient

login()方法如下所示:

login(redirect?: string) {
// Set redirect after login
const _redirect = redirect ? redirect : this.router.url;
localStorage.setItem('auth_redirect', _redirect);
// Auth0 authorize request
this._auth0.authorize();
}

如果将redirect URL段传递到方法中,我们会将其保存在本地存储中。 如果没有传递重定向,我们将只存储当前URL。 然后,我们将使用在成员中创建的_auth0实例,并调用Auth0的authorize()方法转到Auth0登录页面,以便我们的用户可以进行身份​​验证。

接下来的三个方法是handleLoginCallback()getUserInfo()_setSession()

handleLoginCallback() {
this.loading = true;
// When Auth0 hash parsed, get profile
this._auth0.parseHash((err, authResult) => {
if (authResult && authResult.accessToken) {
window.location.hash = '';
// Store access token
this.accessToken = authResult.accessToken;
// Get user info: set up session, get Firebase token
this.getUserInfo(authResult);
} else if (err) {
this.router.navigate(['/']);
this.loading = false;
console.error(`Error authenticating: ${err.error}`);
}
});
}
getUserInfo(authResult) {
// Use access token to retrieve user's profile and set session
this._auth0.client.userInfo(this.accessToken, (err, profile) => {
if (profile) {
this._setSession(authResult, profile);
} else if (err) {
console.warn(`Error retrieving profile: ${err.error}`);
}
});
}
private _setSession(authResult, profile) {
// Set tokens and expiration in localStorage
const expiresAt = JSON.stringify((authResult.expiresIn * 1000) + Date.now());
localStorage.setItem('expires_at', expiresAt);
this.userProfile = profile;
// Session set; set loggedIn and loading
this.loggedIn = true;
this.loading = false;
// Get Firebase token
this._getFirebaseToken();
// Redirect to desired route
this.router.navigateByUrl(localStorage.getItem('auth_redirect'));

这些方法是不言自明的:它们使用Auth0方法parseHash()userInfo()来提取身份验证结果并获取用户的个人资料 。 我们还将设置服务的属性以存储必要的状态(例如,用户的身份验证状态是否正在加载以及是否已登录),处理错误,将数据保存到我们的服务和本地存储中,以及重定向到适当的位置路线。

我们还将使用身份验证结果的访问令牌来向我们的API授权HTTP请求以获取Firebase令牌。 这是通过_getFirebaseToken()_firebaseAuth()方法完成的:

private _getFirebaseToken() {
// Prompt for login if no access token
if (!this.accessToken) {
this.login();
}
const getToken$ = () => {
return this.http
.get(`${environment.apiRoot}auth/firebase`, {
headers: new HttpHeaders().set('Authorization', `Bearer ${this.accessToken}`)
});
};
this.firebaseSub = getToken$().subscribe(
res => this._firebaseAuth(res),
err => console.error(`An error occurred fetching Firebase token: ${err.message}`)
);
}
private _firebaseAuth(tokenObj) {
this.afAuth.auth.signInWithCustomToken(tokenObj.firebaseToken)
.then(res => {
this.loggedInFirebase = true;
// Schedule token renewal
this.scheduleFirebaseRenewal();
console.log('Successfully authenticated with Firebase!');
})
.catch(err => {
const errorCode = err.code;
const errorMessage = err.message;
console.error(`${errorCode} Could not log into Firebase: ${errorMessage}`);
this.loggedInFirebase = false;
});
}

我们将创建一个从GET请求到我们的API的/auth/firebase端点的可观察到的getToken$并订阅它。 如果成功,我们会将带有自定义Firebase令牌的返回对象传递给_firebaseAuth()方法,该方法将使用Firebase的signInWithCustomToken()方法向Firebase进行身份验证。 此方法返回一个Promise,当Promise被解决后,我们可以告诉我们的应用程序Firebase登录成功。 我们还可以安排Firebase令牌续订(我们稍后会介绍)。 我们将适当地处理所有错误。

我们的自定义Firebase令牌将在3600秒(1小时)内到期。 这仅是我们默认的Auth0访问令牌生存期(即7200秒或2个小时)的一半 。 为避免用户在会话过程中意外失去对Firebase的访问权限,我们将使用以下两种方法设置Firebase令牌自动续订: scheduleFirebaseRenewal()unscheduleFirebaseRenewal()

注意:您也可以使用checkSession()方法以类似的方式使用checkSession()实现自动会话更新。 此外,如果用户离开应用程序,然后稍后返回,则可以使用checkSession()在构造函数中还原未到期的身份验证会话。 在本教程中,我们不会涉及到这一点,但是您应该自己尝试一下!

scheduleFirebaseRenewal() {
// If user isn't authenticated, check for Firebase subscription
// and unsubscribe, then return (don't schedule renewal)
if (!this.loggedInFirebase) {
if (this.firebaseSub) {
this.firebaseSub.unsubscribe();
}
return;
}
// Unsubscribe from previous expiration observable
this.unscheduleFirebaseRenewal();
// Create and subscribe to expiration observable
// Custom Firebase tokens minted by Firebase
// expire after 3600 seconds (1 hour)
const expiresAt = new Date().getTime() + (3600 * 1000);
const expiresIn$ = Observable.of(expiresAt)
.pipe(
mergeMap(
expires => {
const now = Date.now();
// Use timer to track delay until expiration
// to run the refresh at the proper time
return Observable.timer(Math.max(1, expires - now));
}
)
);
this.refreshFirebaseSub = expiresIn$
.subscribe(
() => {
console.log('Firebase token expired; fetching a new one');
this._getFirebaseToken();
}
);
}
unscheduleFirebaseRenewal() {
if (this.refreshFirebaseSub) {
this.refreshFirebaseSub.unsubscribe();
}
}

为了安排自动令牌更新,我们将创建一个可观察的计时器,该计时器可以倒计时到令牌的到期时间。 我们可以订阅expiresIn$ observable,然后再次调用_getFirebaseToken()方法以获取新令牌。 signInWithCustomToken() angularfire2 auth方法返回一个Promise。 当承诺解决后, scheduleFirebaseRenewal()调用scheduleFirebaseRenewal() ,这又确保了只要用户登录到我们的应用程序,令牌就将继续更新。

我们还需要能够取消订阅令牌续订,因此我们还将为此创建一个方法。

最后,我们的身份验证服务中的最后两个方法是logout()tokenValid()

logout() {
// Ensure all auth items removed
localStorage.removeItem('expires_at');
localStorage.removeItem('auth_redirect');
this.accessToken = undefined;
this.userProfile = undefined;
this.loggedIn = false;
// Sign out of Firebase
this.loggedInFirebase = false;
this.afAuth.auth.signOut();
// Return to homepage
this.router.navigate(['/']);
}
get tokenValid(): boolean {
// Check if current time is past access token's expiration
const expiresAt = JSON.parse(localStorage.getItem('expires_at'));
return Date.now() < expiresAt;
}

logout()方法从本地存储和我们的服务中删除所有会话信息,退出Firebase Auth,然后将用户重定向回首页(我们应用程序中唯一的公共路由)。

tokenValid访问器方法通过将Auth0访问令牌的到期时间与当前日期时间进行比较来检查Auth0访问令牌是否到期。 这对于确定用户是否需要新的访问令牌很有用。 我们在本教程中不会对此进行介绍,但是您可能希望自己进一步探索Auth0会话续订。

这就是我们的AuthService

回调组件

回想一下,我们在根模块中创建了一个CallbackComponent 。 此外,我们将environment的Auth0 redirect设置为回调组件的路由。 这意味着,当用户使用Auth0登录时,他们将通过/callback路由返回我们的应用,并将身份验证哈希附加到URI。

我们使用方法来处理身份验证和设置会话,从而创建了AuthService ,但是目前尚未从任何地方调用这些方法。 回调组件是执行此代码的适当位置。

打开callback.component.ts文件并添加:

// src/app/callback.component.ts
import { Component, OnInit } from '@angular/core';
import { AuthService } from './auth/auth.service';
@Component({
selector: 'app-callback',
template: `
<app-loading></app-loading>
`
})
export class CallbackComponent implements OnInit {
constructor(private auth: AuthService) { }
ngOnInit() {
this.auth.handleLoginCallback();
}
}

我们所有的回调组件所需要做的就是在AuthServicehandleAuth()方法执行时显示LoadingComponenthandleLoginCallback()方法将解析身份验证哈希,获取用户的个人资料信息,设置其会话,然后重定向到应用程序中的适当路由。

验证卫士

现在,我们已经实现了身份验证服务,我们可以访问在整个Angular应用程序中有效使用身份验证状态所需的属性和方法。 让我们使用此逻辑来实现AuthGuard以保护路由。

使用Angular CLI应该会生成一些有用的样板代码,并且我们只需要进行一些小的更改即可确保只有经过身份验证的用户才能访问我们的受保护路由。

注意:必须注意,路由防护器 本身不能提供足够的安全性。 就像我们在本教程中所做的那样,您应该始终保护自己的API端点,并且永远不要依赖客户端来授权对受保护数据的访问。

打开auth.guard.ts文件并进行以下更改:

// src/app/auth/auth.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { AuthService } from './auth.service';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private auth: AuthService) { }
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
if (this.auth.loggedIn) {
return true;
} else {
// Send guarded route to redirect after logging in
this.auth.login(state.url);
return false;
}
}
}

我们将导入AuthService添加一个constructor()函数,以使该服务在我们的路由保护器中可用。 如果满足条件以授予对路由的访问权,则canActivate()方法应返回true否则返回false 。 在我们的情况下,如果用户经过身份验证,则应该能够访问该受保护的路由。 该loggedIn从我们的财产AuthService提供此信息。

如果用户没有有效的令牌,我们将提示他们登录。我们希望他们在通过身份验证后被重定向回受保护的路由,因此,我们将调用login()方法并传递受保护的路由( state.url )作为重定向参数。

注意:请记住,我们之前设置了整个应用程序的体系结构和路由。 我们已经在我们的狗详细信息路由中添加了AuthGuard ,因此,既然我们已经实现了警卫功能,则应该对其进行保护。

核心逻辑

The last thing we'll do in this section of our tutorial is build out the remaining components and services that belong to our CoreModule . We've already taken care of the LoadingComponent and ErrorComponent , so let's move on to the header.

Header Component

The header will use methods and logic from our authentication service to show login and logout buttons as well as display the user's name and picture if they're authenticated. Open the header.component.ts file and add:

// src/app/core/header/header.component.ts
import { Component } from '@angular/core';
import { AuthService } from '../../auth/auth.service';
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styles: [`
img {
border-radius: 100px;
width: 30px;
}
.loading { line-height: 31px; }
.home-link { color: #212529; }
.home-link:hover { text-decoration: none; }
`]
})
export class HeaderComponent {
constructor(public auth: AuthService) {}
}

We'll add a few simple styles and import our AuthService to make its members publicly available to our header component's template.

Next open the header.component.html file and add:

<!-- src/app/core/header/header.component.html -->
<nav class="nav justify-content-between mt-2 mx-2 mb-3">
<div class="d-flex align-items-center">
<strong class="mr-1"><a routerLink="/" class="home-link">Popular Dogs ❤</a></strong>
</div>
<div class="ml-3">
<small *ngIf="auth.loading" class="loading">
Logging in...
</small>
<ng-template [ngIf]="!auth.loading">
<button
*ngIf="!auth.loggedIn"
class="btn btn-primary btn-sm"
(click)="auth.login()">Log In</button>
<span *ngIf="auth.loggedIn">
<img [src]="auth.userProfile.picture">
<small>{{ auth.userProfile.name }}</small>
<button
class="btn btn-danger btn-sm"
(click)="auth.logout()">Log Out</button>
</span>
</ng-template>
</div>
</nav>

The header now shows:

  • The name of our app (“Popular Dogs”) with a link to the / route
  • A login button if the user is not authenticated
  • A “Logging in…” message if the user is currently authenticating
  • The user's picture, name, and a logout button if the user is authenticated

Now that we have our header component built, we need to display it in our app.

Open the app.component.html file and add:

<!-- src/app/app.component.html -->
<app-header></app-header>
<div class="container">
<router-outlet></router-outlet>
</div>

The header component will now be displayed in our app with the current routed component showing beneath it. Check it out in the browser and try logging in!

Dog and DogDetail Models

Let's implement our dog.ts and dog-detail.ts interfaces . These are models that specify types for the shape of values that we'll use in our app. Using models ensures that our data has the structure that we expect.

We'll start with the dog.ts interface:

// src/app/core/dog.ts
export interface Dog {
breed: string;
rank: number;
image: string;
}

Next let's implement the dog-detail.ts interface:

// src/app/core/dog-detail.ts
export interface DogDetail {
breed: string;
rank: number;
description: string;
personality: string;
energy: string;
group: string;
image: string;
link: string;
}

API Service

With our Node API and models in place, we're ready to implement the service that will call our API in the Angular front end.

Open the api.service.ts file and add this code:

// src/app/core/api.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { environment } from './../../environments/environment';
import { AuthService } from './../auth/auth.service';
import { Observable } from 'rxjs/Observable';
import { catchError } from 'rxjs/operators';
import 'rxjs/add/observable/throw';
import { Dog } from './../core/dog';
import { DogDetail } from './../core/dog-detail';
@Injectable()
export class ApiService {
private _API = `${environment.apiRoot}api`;
constructor(
private http: HttpClient,
private auth: AuthService) { }
getDogs$(): Observable<Dog[]> {
return this.http
.get(`${this._API}/dogs`)
.pipe(
catchError((err, caught) => this._onError(err, caught))
);
}
getDogByRank$(rank: number): Observable<DogDetail> {
return this.http
.get(`${this._API}/dog/${rank}`, {
headers: new HttpHeaders().set('Authorization', `Bearer ${this.auth.accessToken}`)
})
.pipe(
catchError((err, caught) => this._onError(err, caught))
);
}
private _onError(err, caught) {
let errorMsg = 'Error: Unable to complete request.';
if (err instanceof HttpErrorResponse) {
errorMsg = err.message;
if (err.status === 401 || errorMsg.indexOf('No JWT') > -1 || errorMsg.indexOf('Unauthorized') > -1) {
this.auth.login();
}
}
return Observable.throw(errorMsg);
}
}

We'll add the necessary imports to handle HTTP in Angular along with the environment configuration, AuthService , RxJS imports, and Dog and DogDetail models we just created. We'll set up private members for the _API and to store the _accessToken , then make the HttpClient and AuthService available privately to our API service.

Our API methods will return observables that emit one value when the API is either called successfully or an error is thrown. The getDogs$() stream returns an observable with an array of objects that are Dog -shaped. The getDogByRank$(rank) stream requires a numeric rank to be passed in, and will then call the API to retrieve the requested Dog 's data. This API call will send an Authorization header containing the authenticated user's access token.

Finally, we'll create an error handler that checks for errors and assesses if the user is not authenticated and prompts for login if so. The observable will then terminate with an error.

Note: We are using arrow functions to pass parameters to our handler functions for RxJS pipeable operators (such as catchError ). This is done to preserve the scope of the this keyword (see the “No separate this ” section of the MDN arrow functions documentation ).

下一步

We've already accomplished a lot in the first part of our tutorial series. In the next part, we'll finish our Popular Dogs application. In the meantime, here are some additional resources that you may want to check out:

Angular Testing Resources

If you're interested in learning more about testing in Angular, which we did not cover in this tutorial, please check out some of the following resources:

  • Angular – Testing
  • Angular Testing In Depth: Services
  • Angular Testing In Depth: HTTP Services
  • Angular Testing In Depth: Components
  • How to correctly test Angular 4 application with Auth0 integration

其他资源

You can find more resources on Firebase, Auth0, and Angular here:

  • Firebase documentation
  • Cloud Firestore documentation
  • angularfire2 documentation
  • Auth0 documentation
  • Auth0 pricing and features
  • Angular documentation
  • Angular CLI
  • Angular Cheatsheet

In the next installment of our Auth0 + Firebase + Angular tutorial, we'll display data from our dogs API and learn how to set up and implement realtime comments with Firebase ! Check out Authenticating Firebase and Angular with Auth0: Part 2 now.

翻译自: https://www.sitepoint.com/authenticating-firebase-angular-auth0-1/

firebase auth

firebase auth_使用Auth0对Firebase和Angular进行身份验证:第1部分相关推荐

  1. 使用Auth0对Firebase和Angular进行身份验证:第1部分

    本文最初发布在Auth0.com博客上 ,并经许可在此处重新发布. 在这个由两部分组成的教程系列中,我们将学习如何构建一个使用Auth0身份验证保护Node后端和Angular前端安全的应用程序. 我 ...

  2. firebase auth_如何使用auth和实时数据库构建Firebase Angular应用

    firebase auth by Zdravko Kolev 通过Zdravko Kolev 如何使用auth和实时数据库构建Firebase Angular应用 (How to build a Fi ...

  3. firebase登录验证_如何使用Firebase通过三步向身份验证本机添加身份验证

    firebase登录验证 Authentication allows us to secure our apps, or limit access for non-user members. Auth ...

  4. android 短信 代码错误,android – Firebase手机身份验证错误:短信代码已过期

    根据文档实施FireBase手机身份验证后,我遇到了一些问题. >某些号码无法通过身份验证:我使用Airtel作为我的服务提供商. 在日志中,我可以确认代码已经发送但我没有在手机上收到它: D/ ...

  5. firebase登录验证_使用Firebase进行电话号码身份验证

    firebase登录验证 介绍 (Introduction) Ever since Firebase was introduced, I thought it would have a signifi ...

  6. 使用 Vue 3 和 Firebase 进行身份验证

    在构建任何类型的应用程序时,隐私和安全是需要牢记的两个重要因素.随着网络犯罪分子变得越来越先进,身份验证是阻止黑客攻击和保护用户最有价值信息的重要步骤. Firebase 身份验证提供后端服务来帮助对 ...

  7. flutter 图形验证_Flutter Firebase身份验证教程

    flutter 图形验证 在Flutter Firebase身份验证的这篇文章中,我们将了解如何使用Firebase的flutter插件向我们的Firebase应用程序验证用户身份. 如果您不熟悉颤动 ...

  8. vue firebase_如何使用Vue.js,Vuex,Vuetify和Firebase构建SPA:使用Firebase添加身份验证...

    vue firebase 第4部分:了解如何使用Firebase添加身份验证和购物车 (Part 4: learn how to use Firebase to add authentication ...

  9. iOS Firebase身份验证入门

    Firebas e是一个跨平台的实时移动数据库平台,它使编码人员可以专注于自己最擅长的事情-对应用程序进行编码-而不必担心服务器基础结构和数据库建模等DevOps问题. 在Google的支持下,Fir ...

最新文章

  1. 智能算法中终止条件: “最大评估次数” or “最大迭代次数”
  2. upper_bound()与lower_bound函数的使用
  3. linux-2.6.32在mini2440开发板上移植(16)之LED 驱动程序移植
  4. 最小公倍数最大公约数
  5. 全国高等院校英语能力大赛模拟题
  6. 嬴彻首款L3自动驾驶样车发布:自研全栈系统,满足高速全场景工况
  7. Maven pom.xml配置详解
  8. php imagefill,PHP图像处理技术实例总结【绘图、水印、验证码、图像压缩】
  9. Fuchsia之GN与Ninja构建hello world
  10. Spark源码阅读@ListenerBus 的实现
  11. JSON日期时间的处理
  12. Sql Server数据库获取月份英文简写
  13. 2021-5-1 【PTA】【L1-6 不变初心数 (15 分)】
  14. SouceInsight v4 注册机源码
  15. 互联网业务实战(一)--今日头条文章发布实现
  16. S3C2440 SDRAM驱动配置编程
  17. net_write_timeout参数
  18. 欧拉角速率与机体角速度转换详细推导
  19. 给定一个整型数组arr,代表数值不同的纸牌排成一条线。玩家A和玩家B依次拿走每张纸牌,规定玩家A先拿,然后B后拿,但是每个玩家每次只能拿走最左或最右的纸牌,玩家A和玩家B都绝顶聪明。求最后获胜者的分数
  20. linux snmptrap的发送与接收。

热门文章

  1. ZBRUSH 快捷键
  2. 小程序登录状态/手机号码获取
  3. 期货资管系统都有哪些功能?
  4. Android多用户原理和实现
  5. 字符头开发实况(5)
  6. 【毕业设计草计】安全
  7. AIDL oneway 以及in、out、inout参数的理解
  8. 苹果iOS 10.2现死机Bug 只需按下两个键
  9. 移动端实现淡入淡出、旋转、缩放动画
  10. iptables深入解析-mangle篇