使用OAuth 2.0构建React Native应用并进行身份验证

本文最初发布在Okta开发人员博客上 感谢您支持使SitePoint成为可能的合作伙伴。

使用Okta和OpenID Connect(OIDC),您可以轻松地将身份验证集成到React Native应用程序中,而不必自己再次构建它。 OIDC允许您直接根据Okta API进行身份验证,本文向您展示如何在React Native应用程序中进行身份验证。 今天,您将看到如何通过AppAuth库使用OIDC重定向将用户登录到React Native应用程序。

React Native是一个非常漂亮的框架。 与Ionic和其他混合移动框架不同,它允许您使用Web技术(React和JavaScript)来构建本机移动应用。 由于不涉及浏览器或WebView,因此使用React Native开发移动应用程序与使用本机SDK相似,因为您将在模拟器或设备上进行所有测试。 无法像使用Ionic一样在您的浏览器中对其进行测试。 这是有好处的,因为您不必编写在浏览器中和在设备上分别工作的代码。

如果您查看Google趋势,可以发现React Native在原生开发方面比Android和iOS还要受欢迎!

今天,我将向您展示如何开发具有最新和最佳版本的React Native应用程序。 在撰写本文时,这是React 16.2.0和React Native 0.54.0。 您将创建一个新应用,添加AppAuth进行身份验证,使用Okta进行身份验证,然后查看该应用程序是否同时在iOS和Android上运行。

AppAuth是用于本机应用程序的客户端SDK,可使用OAuth 2.0和OpenID Connect对最终用户进行身份验证和授权。 它可用于iOS,macOS,Android和Native JS环境,实现了针对本机应用程序身份验证和授权的现代安全性和可用性最佳实践

创建您的React本机应用程序

React有一个create-react-app命令行工具(CLI),可用于创建新的React应用。 React Native有一个类似的工具,称为Create React Native App 。 在安装它之前,请确保已安装Node v6或更高版本。

安装create-react-native-app并创建一个名为okta-rn的新项目:

npm install -g create-react-native-app
create-react-native-app okta-rn
cd okta-rn
npm start

运行这些命令将导致您的终端提示您一些选项:

To view your app with live reloading, point the Expo app to this QR code.
You'll find the QR scanner on the Projects tab of the app.

[QR Code]

Or enter this address in the Expo app's search bar:

  exp://172.31.98.12:19000

Your phone will need to be on the same local network as this computer.
For links to install the Expo app, please visit https://expo.io.

Logs from serving your app will appear here. Press Ctrl+C at any time to stop.

 › Press a to open Android device or emulator, or i to open iOS emulator.
 › Press q to display QR code.
 › Press r to restart packager, or R to restart packager and clear cache.
 › Press d to toggle development mode. (current mode: development)

如果您使用的是Mac,请按i打开iOS模拟器。 系统将提示您使用Expo安装/打开,然后提供呈现的App.js

在世博会开幕

呈现的App.js

如果您使用的是Windows或Linux,建议您尝试使用Android模拟器或Android设备(如果有)。 如果它不起作用,请不用担心,我稍后将向您展示如何进行该工作。

提示:您可以使用Microsoft的TypeScript React Native Starter在React Native应用程序中使用TypeScript代替JavaScript。 如果您决定走这条路,建议您完成本教程后,按照以下步骤转换应用程序。

React Native和OAuth 2.0

在此示例中,我将使用React Native App Auth (由Formidable创建的库)。 我使用此库的原因有三点:1)他们提供了一个很好的示例 ,使我能够在短短的几分钟内完成工作; 2)它使用AppAuth(成熟的OAuth客户端实现),以及3)无法使其他任何工作。

  • 我尝试了react-native-oauth,但发现添加新的提供程序之前需要使用现有的提供程序。 我只想让Okta作为提供者。 此外,问题数量很多,并且拉取请求也作为警告信号。
  • 我尝试了react-native-simple-auth,但是在使不赞成使用的Navigator组件与最新的React Native版本一起使用时遇到了问题。
  • 我尝试使用React Native教程进行OAuth 2操作 ,但是在重定向回我的应用程序时也遇到了问题。

在Okta中创建本机应用程序

在将AppAuth添加到React Native应用程序之前,需要一个应用程序进行授权。 如果您没有永久的Okta Developer永久帐户,请立即获取一个

登录到您的Okta Developer帐户,然后导航到Applications > Add Application 。 单击本 ,然后单击下一步 。 给应用程序起一个您会记住的名称(例如React Native ),除了默认的Authorization Code之外,选择Refresh Token作为授予类型。 复制登录重定向URI (例如com.oktapreview.dev-158606:/callback )并将其保存在某处。 在配置应用程序时,您将需要此值。

单击完成 ,您应该在下一个屏幕上看到客户端ID。 也复制并保存该值。

添加React Native AppAuth进行身份验证

您需要“弹出”应用程序的本机配置,通常由create-react-native-app隐藏。

npm run eject

当提示您回答问题时,请使用以下答案:

回答
您想如何从create-react-native-app中退出? React Native
您的应用应在用户主屏幕上显示为什么? Okta RN
您的Android Studio和Xcode项目应命名为什么? OktaRN

要为React Native安装App Auth,请运行以下命令:

npm i react-native-app-auth@2.2.0
npm i
react-native link

运行这些命令后,您必须配置本机iOS项目 。 为了方便起见,我已复制以下步骤。

iOS设置

React Native App Auth取决于AppAuth-ios ,因此您必须将其配置为依赖项。 最简单的方法是使用CocoaPods 。 要安装CocoaPods,请运行以下命令:

sudo gem install cocoapods

在项目的ios目录中创建一个Podfile ,将AppAuth-ios指定为依赖项。 确保OktaRN与您在运行npm run eject OktaRN时指定的应用程序名称匹配。

platform :ios, '11.0'

target 'OktaRN' do
  pod 'AppAuth', '>= 0.91'
end

然后从ios目录运行pod install 。 即使是快速连接,第一次也可能需要一段时间。 现在是喝咖啡或苏格兰威士忌的好时机! 🥃

通过从ios目录运行open OktaRN.xcworkspace在Xcode中打开项目。

如果打算支持iOS 10及更早版本,则需要在ios/OktaRN/Info.plist定义支持的重定向URL方案,如下所示:

<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLName</key>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>{yourReversedOktaDomain}</string>
    </array>
  </dict>
</array>

下面是我更改应用程序标识符并添加此密钥后的样子。

<key>CFBundleIdentifier</key>
<string>com.okta.developer.reactnative.$(PRODUCT_NAME:rfc1034identifier)</string>
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLName</key>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>com.oktapreview.dev-158606</string>
    </array>
  </dict>
</array>

在Xcode项目中打开AppDelegate.h (OktaRN> OktaRN> AppDelegate.h ),并在下面添加带有+的行。

+ @protocol OIDAuthorizationFlowSession;

  @interface AppDelegate : UIResponder <UIApplicationDelegate>
+ @property(nonatomic, strong, nullable) id<OIDAuthorizationFlowSession> currentAuthorizationFlow;
  @property (nonatomic, strong) UIWindow *window;
  @end

此属性保存在重定向到Okta之前启动的授权流信息。 Okta授权您后,它将重定向到传入的redirect_uri

授权流程从openURL()应用程序委托方法开始。 要添加它,请打开AppDelegate.m并导入AppAuth.h

#import "AppAuth.h"

然后在类的底部( @end之前),添加openURL()方法。

- (BOOL)application:(UIApplication *)app
            openURL:(NSURL *)url
            options:(NSDictionary<NSString *, id> *)options {
  if ([_currentAuthorizationFlow resumeAuthorizationFlowWithURL:url]) {
    _currentAuthorizationFlow = nil;
    return YES;
  }
  return NO;
}

构建您的React Native应用

用以下JavaScript替换App.js的代码。 此代码使您可以授权,刷新访问令牌并撤销它。

import React, { Component } from 'react';
import { UIManager, LayoutAnimation } from 'react-native';
import { authorize, refresh, revoke } from 'react-native-app-auth';
import { Page, Button, ButtonContainer, Form, Heading } from './components';

UIManager.setLayoutAnimationEnabledExperimental &&
  UIManager.setLayoutAnimationEnabledExperimental(true);

const scopes = ['openid', 'profile', 'email', 'offline_access'];

type State = {
  hasLoggedInOnce: boolean,
  accessToken: ?string,
  accessTokenExpirationDate: ?string,
  refreshToken: ?string
};

const config = {
  issuer: 'https://{yourOktaDomain}/oauth2/default',
  clientId: '{clientId}',
  redirectUrl: 'com.{yourReversedOktaDomain}:/callback',
  additionalParameters: {},
  scopes: ['openid', 'profile', 'email', 'offline_access']
};

export default class App extends Component<{}, State> {
  state = {
    hasLoggedInOnce: false,
    accessToken: '',
    accessTokenExpirationDate: '',
    refreshToken: ''
  };

  animateState(nextState: $Shape<State>, delay: number = 0) {
    setTimeout(() => {
      this.setState(() => {
        LayoutAnimation.easeInEaseOut();
        return nextState;
      });
    }, delay);
  }

  authorize = async () => {
    try {
      const authState = await authorize(config);
      this.animateState(
        {
          hasLoggedInOnce: true,
          accessToken: authState.accessToken,
          accessTokenExpirationDate: authState.accessTokenExpirationDate,
          refreshToken: authState.refreshToken
        },
        500
      );
    } catch (error) {
      Alert.alert('Failed to log in', error.message);
    }
  };

  refresh = async () => {
    try {
      const authState = await refresh(config, {
        refreshToken: this.state.refreshToken
      });

      this.animateState({
        accessToken: authState.accessToken || this.state.accessToken,
        accessTokenExpirationDate:
          authState.accessTokenExpirationDate || this.state.accessTokenExpirationDate,
        refreshToken: authState.refreshToken || this.state.refreshToken
      });
    } catch (error) {
      Alert.alert('Failed to refresh token', error.message);
    }
  };

  revoke = async () => {
    try {
      await revoke(config, {
        tokenToRevoke: this.state.accessToken,
        sendClientId: true
      });
      this.animateState({
        accessToken: '',
        accessTokenExpirationDate: '',
        refreshToken: ''
      });
    } catch (error) {
      Alert.alert('Failed to revoke token', error.message);
    }
  };

  render() {
    const {state} = this;
    return (
      <Page>
        {!!state.accessToken ? (
          <Form>
            <Form.Label>accessToken</Form.Label>
            <Form.Value>{state.accessToken}</Form.Value>
            <Form.Label>accessTokenExpirationDate</Form.Label>
            <Form.Value>{state.accessTokenExpirationDate}</Form.Value>
            <Form.Label>refreshToken</Form.Label>
            <Form.Value>{state.refreshToken}</Form.Value>
          </Form>
        ) : (
          <Heading>{state.hasLoggedInOnce ? 'Goodbye.' : 'Hello, stranger.'}</Heading>
        )}

        <ButtonContainer>
          {!state.accessToken && (
            <Button onPress={this.authorize} text="Authorize" color="#017CC0"/>
          )}
          {!!state.refreshToken && <Button onPress={this.refresh} text="Refresh" color="#24C2CB"/>}
          {!!state.accessToken && <Button onPress={this.revoke} text="Revoke" color="#EF525B"/>}
        </ButtonContainer>
      </Page>
    );
  }
}

确保使用您的设置调整config

const config = {
  issuer: 'https://{yourOktaDomain}/oauth2/default',
  clientId: '{clientId}',
  redirectUrl: 'com.{yourReversedOktaDomain}:/callback',
  ...
};

更改index.js以使用OktaRN作为应用程序的名称。

AppRegistry.registerComponent('OktaRN', () => App);

这段代码使用了styled-components ,因此您需要将其安装为依赖项。

注意:在运行以下命令之前,请确保导航到项目的根目录。

npm i styled-components

然后从Formidable的示例中将components目录复制到项目的根目录中。

svn export https://github.com/FormidableLabs/react-native-app-auth/trunk/Example/components

也获取Page.js组件中引用的背景图像。

svn export https://github.com/FormidableLabs/react-native-app-auth/trunk/Example/assets

在iOS模拟器上运行

使用npm run ios运行您的应用npm run ios

您应该看到一个屏幕,上面写着“你好,陌生人”。 点击授权 ,系统将提示您继续或取消。

你好,陌生人

取消或继续

单击继续 ,您应该会看到Okta登录表单。 输入您的凭据,您将被重定向回该应用程序。

Okta登录

访问令牌信息

您可以单击刷新以查看访问令牌的值和到期日期更改。

提示:如果动画在iOS Simulator中缓慢发生,请切换调试 > 慢动画

Android设置

要配置本机Android项目,请先升级其使用的Gradle版本。

cd android
./gradlew wrapper --gradle-version 4.6

适用于Android的React Native App Auth取决于AppAuth-android ,但是您需要向项目中添加正确的Android支持库版本。

将Google Maven存储库添加到您的android/build.gradle并升级Android Tools依赖项:

buildscript {
    repositories {
        jcenter()
        google()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.1'
    }
}

android/app/build.gradleappcompat依赖项升级到25.3.1以匹配AppAuth期望的依赖项。

dependencies {
  compile "com.android.support:appcompat-v7:25.3.1"
}

删除不再需要的buildToolsVersion "23.0.1"

更新compileSdkVersion

android {
  compileSdkVersion 25
}

android/app/build.gradle中将appAuthRedirectScheme属性添加为defaultConfig

android {
  defaultConfig {
    ...
    manifestPlaceholders = [
      appAuthRedirectScheme: '{yourReversedOktaDomain}'
    ]
  }
}

进行此更改后,我的defaultConfig如下所示。

defaultConfig {
    applicationId "com.oktarn"
    minSdkVersion 16
    targetSdkVersion 22
    versionCode 1
    versionName "1.0"
    ndk {
        abiFilters "armeabi-v7a", "x86"
    }
    manifestPlaceholders = [
        appAuthRedirectScheme: 'com.oktapreview.dev-158606'
    ]
}

在Android上运行

要在Android模拟器上进行尝试,请运行npm run android 。 如果您没有插入电话或没有运行Android虚拟设备(AVD),则会看到错误消息:

* What went wrong:
Execution failed for task ':app:installDebug'.
> com.android.builder.testing.api.DeviceException: No connected devices!

要解决此问题,请打开Android Studio,选择打开现有项目 ,然后在项目中选择android目录。 如果系统提示您更新任何内容,请批准它。

要创建新的AVD,请导航至工具 > Android > AVD Manager 。 创建一个新的虚拟设备,然后单击播放。 从下面的设置中可以看到,我选择了Pixel 2。

AVD像素2

再次运行npm run android 。 您应该会看到一个欢迎屏幕,并且能够成功进行授权。

你好,陌生人

Okta登录

在Android上访问令牌

提示:不建议 Configuration 'compile' in project ':app' is deprecated. Use 'implementation' instead.修复Configuration 'compile' in project ':app' is deprecated. Use 'implementation' instead. Configuration 'compile' in project ':app' is deprecated. Use 'implementation' instead. ,将dependencies下的compile更改为implementation 。 有关更多信息,请参见“ 迁移到Gradle 3.0.0的Android插件”

升级到最新版本的React Native

react-native-git-upgrade工具是将项目升级为使用最新版本的便捷方法。 安装并运行它。

npm i -g react-native-git-upgrade
react-native-git-upgrade
npm i

或者,您可以只将package.json更改为"react-native": "0.54.2" ,然后运行npm i

获取和查看ID令牌

如果除了访问令牌之外还想获取ID令牌,请添加idToken作为State类型的属性,并在App.js添加state变量。

type State = {
  ...
  idToken: ?string
};

export default class App extends Component<{}, State> {
  ...
  state = {
    ...
    idToken: ''
  };

然后更新authorize()方法以从authState设置属性。 您将需要在refresh()revoke()方法中添加类似的逻辑。

authorize = async () => {
  try {
    const authState = await authorize(config);
    this.animateState(
      {
        hasLoggedInOnce: true,
        accessToken: authState.accessToken,
        accessTokenExpirationDate: authState.accessTokenExpirationDate,
        refreshToken: authState.refreshToken,
        idToken: authState.idToken
      },
      500
    );
  } catch (error) {
    Alert.alert('Failed to log in', error.message);
  }
};

要查看ID令牌中的内容,请安装buffer

npm i buffer

将其导入App.js的顶部。

import { Buffer } from 'buffer';

然后更改render()方法对其进行解码。

render() {
  const {state} = this;
  if (state.idToken) {
    const jwtBody = state.idToken.split('.')[1];
    const base64 = jwtBody.replace('-', '+').replace('_', '/');
    const decodedJwt = Buffer.from(base64, 'base64');
    state.idTokenJSON = JSON.parse(decodedJwt);
  }
  ...

最后,在显示访问令牌的行之后添加<Form.Label><Form.Value>行。

<Form.Label>ID Token</Form.Label>
<Form.Value>{JSON.stringify(state.idTokenJSON)}</Form.Value>

运行npm run ios (或npm run android ),使用Okta授权后,您应该在ID令牌中看到声明。 下面是一个截图,证明它可以在iOS Simulator中使用。

iOS上的ID令牌

使用您的访问令牌调用API

现在您有了访问令牌,该怎么办? 您可以在Authorization标头中调用受Okta保护的API!

我写了关于如何使用Spring Boot和ReactBootiful Development中创建“ Good Beers” API的文章 。 您可以使用该应用程序的后端来证明它可以工作。

从GitHub克隆项目,并查看okta分支。

git clone https://github.com/oktadeveloper/spring-boot-react-example.git
git checkout okta

修改spring-boot-react-example/server/src/main/resources/application.properties以设置issuerclientId

okta.oauth2.issuer=https://{yourOktaDomain}/oauth2/default
okta.oauth2.clientId={clientId}

注意:您需要安装Java 8才能运行此Spring Boot应用程序。

通过从server目录运行./mvnw来启动应用程序。

回到React Native客户端。 在App.js ,将beers添加为state的属性。

state = {
  ...
  beers: []
};

revoke()方法中将其设置为相同的值。 添加一个fetchGoodBeers()方法,该方法使用访问令牌来调用后端。

fetchGoodBeers = async () => {
  if (this.state.beers.length) {
    // reset to id token if beers is already populated
    this.animateState({beers: []})
  } else {
    fetch('http://localhost:8080/good-beers', {
      headers: {
        'Authorization': `Bearer ${this.state.accessToken}`
      }
    }).then(response => response.json())
      .then(data => {
        this.animateState({beers: data})
      })
      .catch(error => console.error(error));
  }
};

提示:为了使其在Android模拟器(和真实电话)中正常运行,您需要将localhost更改为您的IP地址。

在底部的<ButtonContainer>中,添加一个“ Good Beers”按钮,使您可以调用API,然后再次按一下以查看ID令牌。

{!!state.accessToken && <Button onPress={this.fetchGoodBeers} text={!this.state.beers.length ? 'Good Beers' : 'ID Token'} color="#008000" />}

修改显示ID令牌的行,以显示API中的JSON。

<Form.Label>{state.beers.length ? 'Good Beers' : 'ID Token'}</Form.Label>
<Form.Value>{JSON.stringify(state.beers.length ? state.beers : state.idTokenJSON)}</Form.Value>

在iOS Simulator中,按Command + R重新加载所有内容,并且在单击Good Beers按钮时应该看到JSON。 您可以使用Command + M (在Mac上为CTRL + M,在其他操作系统上)在Android中重新加载。

iOS上的好啤酒

Android上的好啤酒

注意: react-native-app-auth中存在一个未解决的问题 ,即由于未发送Authorization标头,因此撤销不能与Okta一起使用。

了解有关React Native和React的更多信息

我希望您喜欢这个如何使用Okta和React Native进行身份验证的旋风之旅。 您可以在React Native 的官方网站上了解更多信息。 您还可以在GitHub上添加约6万颗星。

您可以在https://github.com/oktadeveloper/okta-react-native-app-auth-example中找到此应用程序的源代码。

如果您有兴趣了解如何使用Okta进行常规的React开发,建议您查看以下资源:

如果您对本文有任何疑问,请在Twitter @mraible上打我。

From: https://www.sitepoint.com/build-a-react-native-application-authenticate-with-oauth-2-0/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值