├── .editorconfig
├── .gitattributes
├── .github
├── actions
│ └── setup
│ │ └── action.yml
└── workflows
│ └── ci.yml
├── .gitignore
├── .nvmrc
├── .watchmanconfig
├── .yarnrc
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── babel.config.js
├── example
├── .gitignore
├── App.js
├── app.json
├── assets
│ ├── adaptive-icon.png
│ ├── favicon.png
│ ├── icon.png
│ └── splash.png
├── babel.config.js
├── metro.config.js
├── package.json
├── src
│ └── App.tsx
├── tsconfig.json
├── webpack.config.js
└── yarn.lock
├── lefthook.yml
├── package-lock.json
├── package.json
├── scripts
└── bootstrap.js
├── src
├── __tests__
│ └── index.test.tsx
├── contexts
│ ├── AuthContext.ts
│ └── AuthProvider.tsx
├── hooks
│ └── useAuth.ts
├── index.tsx
├── interfaces
│ ├── AuthConfig.ts
│ ├── AuthContextType.ts
│ ├── AuthProviderProps.ts
│ ├── AuthResponse.ts
│ └── User.ts
├── services
│ └── AuthService.ts
└── utils
│ ├── TokenStorage.native.ts
│ └── TokenStorage.ts
├── tsconfig.build.json
├── tsconfig.json
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 |
9 | indent_style = space
10 | indent_size = 2
11 |
12 | end_of_line = lf
13 | charset = utf-8
14 | trim_trailing_whitespace = true
15 | insert_final_newline = true
16 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj -text
2 | # specific for windows script files
3 | *.bat text eol=crlf
--------------------------------------------------------------------------------
/.github/actions/setup/action.yml:
--------------------------------------------------------------------------------
1 | name: Setup
2 | description: Setup Node.js and install dependencies
3 |
4 | runs:
5 | using: composite
6 | steps:
7 | - name: Setup Node.js
8 | uses: actions/setup-node@v3
9 | with:
10 | node-version-file: .nvmrc
11 |
12 | - name: Cache dependencies
13 | id: yarn-cache
14 | uses: actions/cache@v3
15 | with:
16 | path: |
17 | **/node_modules
18 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('**/package.json') }}
19 | restore-keys: |
20 | ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
21 | ${{ runner.os }}-yarn-
22 |
23 | - name: Install dependencies
24 | if: steps.yarn-cache.outputs.cache-hit != 'true'
25 | run: |
26 | yarn install --cwd example --frozen-lockfile
27 | yarn install --frozen-lockfile
28 | shell: bash
29 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | push:
4 | branches:
5 | - main
6 | pull_request:
7 | branches:
8 | - main
9 |
10 | jobs:
11 | lint:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v3
16 |
17 | - name: Setup
18 | uses: ./.github/actions/setup
19 |
20 | - name: Lint files
21 | run: yarn lint
22 |
23 | - name: Typecheck files
24 | run: yarn typecheck
25 |
26 | test:
27 | runs-on: ubuntu-latest
28 | steps:
29 | - name: Checkout
30 | uses: actions/checkout@v3
31 |
32 | - name: Setup
33 | uses: ./.github/actions/setup
34 |
35 | - name: Run unit tests
36 | run: yarn test --maxWorkers=2 --coverage
37 |
38 | build-library:
39 | runs-on: ubuntu-latest
40 | steps:
41 | - name: Checkout
42 | uses: actions/checkout@v3
43 |
44 | - name: Setup
45 | uses: ./.github/actions/setup
46 |
47 | - name: Build package
48 | run: yarn prepack
49 |
50 | build-web:
51 | runs-on: ubuntu-latest
52 | steps:
53 | - name: Checkout
54 | uses: actions/checkout@v3
55 |
56 | - name: Setup
57 | uses: ./.github/actions/setup
58 |
59 | - name: Build example for Web
60 | run: |
61 | yarn example expo export:web
62 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # XDE
6 | .expo/
7 |
8 | # VSCode
9 | .vscode/
10 | jsconfig.json
11 |
12 | # Xcode
13 | #
14 | build/
15 | *.pbxuser
16 | !default.pbxuser
17 | *.mode1v3
18 | !default.mode1v3
19 | *.mode2v3
20 | !default.mode2v3
21 | *.perspectivev3
22 | !default.perspectivev3
23 | xcuserdata
24 | *.xccheckout
25 | *.moved-aside
26 | DerivedData
27 | *.hmap
28 | *.ipa
29 | *.xcuserstate
30 | project.xcworkspace
31 |
32 | # Android/IJ
33 | #
34 | .classpath
35 | .cxx
36 | .gradle
37 | .idea
38 | .project
39 | .settings
40 | local.properties
41 | android.iml
42 |
43 | # Cocoapods
44 | #
45 | example/ios/Pods
46 |
47 | # Ruby
48 | example/vendor/
49 |
50 | # node.js
51 | #
52 | node_modules/
53 | npm-debug.log
54 | yarn-debug.log
55 | yarn-error.log
56 |
57 | # BUCK
58 | buck-out/
59 | \.buckd/
60 | android/app/libs
61 | android/keystores/debug.keystore
62 |
63 | # Expo
64 | .expo/
65 |
66 | # Turborepo
67 | .turbo/
68 |
69 | # generated by bob
70 | lib/
71 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v18
2 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/.yarnrc:
--------------------------------------------------------------------------------
1 | # Override Yarn command so we can automatically setup the repo on running `yarn`
2 |
3 | yarn-path "scripts/bootstrap.js"
4 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 |
2 | # Contributor Covenant Code of Conduct
3 |
4 | ## Our Pledge
5 |
6 | We as members, contributors, and leaders pledge to make participation in our
7 | community a harassment-free experience for everyone, regardless of age, body
8 | size, visible or invisible disability, ethnicity, sex characteristics, gender
9 | identity and expression, level of experience, education, socio-economic status,
10 | nationality, personal appearance, race, caste, color, religion, or sexual
11 | identity and orientation.
12 |
13 | We pledge to act and interact in ways that contribute to an open, welcoming,
14 | diverse, inclusive, and healthy community.
15 |
16 | ## Our Standards
17 |
18 | Examples of behavior that contributes to a positive environment for our
19 | community include:
20 |
21 | * Demonstrating empathy and kindness toward other people
22 | * Being respectful of differing opinions, viewpoints, and experiences
23 | * Giving and gracefully accepting constructive feedback
24 | * Accepting responsibility and apologizing to those affected by our mistakes,
25 | and learning from the experience
26 | * Focusing on what is best not just for us as individuals, but for the overall
27 | community
28 |
29 | Examples of unacceptable behavior include:
30 |
31 | * The use of sexualized language or imagery, and sexual attention or advances of
32 | any kind
33 | * Trolling, insulting or derogatory comments, and personal or political attacks
34 | * Public or private harassment
35 | * Publishing others' private information, such as a physical or email address,
36 | without their explicit permission
37 | * Other conduct which could reasonably be considered inappropriate in a
38 | professional setting
39 |
40 | ## Enforcement Responsibilities
41 |
42 | Community leaders are responsible for clarifying and enforcing our standards of
43 | acceptable behavior and will take appropriate and fair corrective action in
44 | response to any behavior that they deem inappropriate, threatening, offensive,
45 | or harmful.
46 |
47 | Community leaders have the right and responsibility to remove, edit, or reject
48 | comments, commits, code, wiki edits, issues, and other contributions that are
49 | not aligned to this Code of Conduct, and will communicate reasons for moderation
50 | decisions when appropriate.
51 |
52 | ## Scope
53 |
54 | This Code of Conduct applies within all community spaces, and also applies when
55 | an individual is officially representing the community in public spaces.
56 | Examples of representing our community include using an official e-mail address,
57 | posting via an official social media account, or acting as an appointed
58 | representative at an online or offline event.
59 |
60 | ## Enforcement
61 |
62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
63 | reported to the community leaders responsible for enforcement at
64 | info@marcelwagner.dev.
65 | All complaints will be reviewed and investigated promptly and fairly.
66 |
67 | All community leaders are obligated to respect the privacy and security of the
68 | reporter of any incident.
69 |
70 | ## Enforcement Guidelines
71 |
72 | Community leaders will follow these Community Impact Guidelines in determining
73 | the consequences for any action they deem in violation of this Code of Conduct:
74 |
75 | ### 1. Correction
76 |
77 | **Community Impact**: Use of inappropriate language or other behavior deemed
78 | unprofessional or unwelcome in the community.
79 |
80 | **Consequence**: A private, written warning from community leaders, providing
81 | clarity around the nature of the violation and an explanation of why the
82 | behavior was inappropriate. A public apology may be requested.
83 |
84 | ### 2. Warning
85 |
86 | **Community Impact**: A violation through a single incident or series of
87 | actions.
88 |
89 | **Consequence**: A warning with consequences for continued behavior. No
90 | interaction with the people involved, including unsolicited interaction with
91 | those enforcing the Code of Conduct, for a specified period of time. This
92 | includes avoiding interactions in community spaces as well as external channels
93 | like social media. Violating these terms may lead to a temporary or permanent
94 | ban.
95 |
96 | ### 3. Temporary Ban
97 |
98 | **Community Impact**: A serious violation of community standards, including
99 | sustained inappropriate behavior.
100 |
101 | **Consequence**: A temporary ban from any sort of interaction or public
102 | communication with the community for a specified period of time. No public or
103 | private interaction with the people involved, including unsolicited interaction
104 | with those enforcing the Code of Conduct, is allowed during this period.
105 | Violating these terms may lead to a permanent ban.
106 |
107 | ### 4. Permanent Ban
108 |
109 | **Community Impact**: Demonstrating a pattern of violation of community
110 | standards, including sustained inappropriate behavior, harassment of an
111 | individual, or aggression toward or disparagement of classes of individuals.
112 |
113 | **Consequence**: A permanent ban from any sort of public interaction within the
114 | community.
115 |
116 | ## Attribution
117 |
118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
119 | version 2.1, available at
120 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
121 |
122 | Community Impact Guidelines were inspired by
123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
124 |
125 | For answers to common questions about this code of conduct, see the FAQ at
126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
127 | [https://www.contributor-covenant.org/translations][translations].
128 |
129 | [homepage]: https://www.contributor-covenant.org
130 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
131 | [Mozilla CoC]: https://github.com/mozilla/diversity
132 | [FAQ]: https://www.contributor-covenant.org/faq
133 | [translations]: https://www.contributor-covenant.org/translations
134 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are always welcome, no matter how large or small!
4 |
5 | We want this community to be friendly and respectful to each other. Please follow it in all your interactions with the project. Before contributing, please read the [code of conduct](./CODE_OF_CONDUCT.md).
6 |
7 | ## Development workflow
8 |
9 | To get started with the project, run `yarn` in the root directory to install the required dependencies for each package:
10 |
11 | ```sh
12 | yarn
13 | ```
14 |
15 | > While it's possible to use [`npm`](https://github.com/npm/cli), the tooling is built around [`yarn`](https://classic.yarnpkg.com/), so you'll have an easier time if you use `yarn` for development.
16 |
17 | While developing, you can run the [example app](/example/) to test your changes. Any changes you make in your library's JavaScript code will be reflected in the example app without a rebuild. If you change any native code, then you'll need to rebuild the example app.
18 |
19 | To start the packager:
20 |
21 | ```sh
22 | yarn example start
23 | ```
24 |
25 | To run the example app on Android:
26 |
27 | ```sh
28 | yarn example android
29 | ```
30 |
31 | To run the example app on iOS:
32 |
33 | ```sh
34 | yarn example ios
35 | ```
36 |
37 | To run the example app on Web:
38 |
39 | ```sh
40 | yarn example web
41 | ```
42 |
43 | Make sure your code passes TypeScript and ESLint. Run the following to verify:
44 |
45 | ```sh
46 | yarn typecheck
47 | yarn lint
48 | ```
49 |
50 | To fix formatting errors, run the following:
51 |
52 | ```sh
53 | yarn lint --fix
54 | ```
55 |
56 | Remember to add tests for your change if possible. Run the unit tests by:
57 |
58 | ```sh
59 | yarn test
60 | ```
61 |
62 |
63 | ### Commit message convention
64 |
65 | We follow the [conventional commits specification](https://www.conventionalcommits.org/en) for our commit messages:
66 |
67 | - `fix`: bug fixes, e.g. fix crash due to deprecated method.
68 | - `feat`: new features, e.g. add new method to the module.
69 | - `refactor`: code refactor, e.g. migrate from class components to hooks.
70 | - `docs`: changes into documentation, e.g. add usage example for the module..
71 | - `test`: adding or updating tests, e.g. add integration tests using detox.
72 | - `chore`: tooling changes, e.g. change CI config.
73 |
74 | Our pre-commit hooks verify that your commit message matches this format when committing.
75 |
76 | ### Linting and tests
77 |
78 | [ESLint](https://eslint.org/), [Prettier](https://prettier.io/), [TypeScript](https://www.typescriptlang.org/)
79 |
80 | We use [TypeScript](https://www.typescriptlang.org/) for type checking, [ESLint](https://eslint.org/) with [Prettier](https://prettier.io/) for linting and formatting the code, and [Jest](https://jestjs.io/) for testing.
81 |
82 | Our pre-commit hooks verify that the linter and tests pass when committing.
83 |
84 | ### Publishing to npm
85 |
86 | We use [release-it](https://github.com/release-it/release-it) to make it easier to publish new versions. It handles common tasks like bumping version based on semver, creating tags and releases etc.
87 |
88 | To publish new versions, run the following:
89 |
90 | ```sh
91 | yarn release
92 | ```
93 |
94 | ### Scripts
95 |
96 | The `package.json` file contains various scripts for common tasks:
97 |
98 | - `yarn bootstrap`: setup project by installing all dependencies and pods.
99 | - `yarn typecheck`: type-check files with TypeScript.
100 | - `yarn lint`: lint files with ESLint.
101 | - `yarn test`: run unit tests with Jest.
102 | - `yarn example start`: start the Metro server for the example app.
103 | - `yarn example android`: run the example app on Android.
104 | - `yarn example ios`: run the example app on iOS.
105 |
106 | ### Sending a pull request
107 |
108 | > **Working on your first pull request?** You can learn how from this _free_ series: [How to Contribute to an Open Source Project on GitHub](https://app.egghead.io/playlists/how-to-contribute-to-an-open-source-project-on-github).
109 |
110 | When you're sending a pull request:
111 |
112 | - Prefer small pull requests focused on one change.
113 | - Verify that linters and tests are passing.
114 | - Review the documentation to make sure it looks good.
115 | - Follow the pull request template when opening a pull request.
116 | - For pull requests that change the API or implementation, discuss with maintainers first by opening an issue.
117 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Marcel Wagner
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Native Laravel Sanctum
2 |
3 | React Native auth package for [Laravel Sanctum ](https://laravel.com/docs/10.x/sanctum) integration. While this package is mainly build for Laravel Sanctum APIs, you can use this library for other API-Backends as well.
4 |
5 | ## Table of Contents
6 |
7 | - [Features](#features)
8 | - [Installation](#installation)
9 | - [Basic Usage](#basic-usage)
10 | - [Example App](#example-app)
11 | - [Documentation](#documentation)
12 | - [Contributing](#contributing)
13 | - [License](#license)
14 |
15 | ## Features
16 |
17 | - [x] Login
18 | - [x] Logout
19 | - [x] Get current user
20 | - [x] Check if user is authenticated
21 | - [x] CSRF-Token
22 |
23 | ## Installation
24 |
25 | ```sh
26 | npm install react-native-laravel-sanctum
27 | ```
28 | ### Install native dependencies
29 | As the package relies on `expo-secure-store` you will need to create a new build of your app to use it.
30 |
31 | ## Basic Usage
32 |
33 | Wrap your app with `AuthProvider` and pass the `config` object as a prop.
34 |
35 | Make sure your validation rules, within your controller look like this:
36 | ```php
37 | [
38 | 'email' => 'required|email',
39 | 'password' => 'required',
40 | 'deviceName' => 'required',
41 | ];
42 | ```
43 |
44 | ```js
45 | import { AuthProvider } from 'react-native-laravel-sanctum';
46 |
47 |
48 | export default function App() {
49 | const config = {
50 | loginUrl: 'https://your-awesome-domain/api/sanctum/token',
51 | logoutUrl: 'https://your-awesome-domain/api/logout',
52 | userUrl: 'https://your-awesome-domain/api/user',
53 | csrfTokenUrl: 'https://your-awesome-domain/sanctum/csrf-cookie'
54 | };
55 |
56 | return (
57 |
58 |
59 | ...
60 |
61 |
62 | );
63 | }
64 | ```
65 |
66 | You can now use the `useAuth` hook within the AuthProvider to access the auth methods.
67 |
68 | ```js
69 | import { useAuth } from 'react-native-laravel-sanctum';
70 |
71 | export default function Login() {
72 | const { login, getToken, updateUser, setUserIsAuthenticated, isAuthenticated, logout, currentUser } = useAuth();
73 | const [email, setEmail] = useState('');
74 | const [password, setPassword] = useState('');
75 |
76 | const handleLogin = async () => {
77 | const tokenObtained = await login(email, password, 'Test-Device');
78 | if (tokenObtained) {
79 | const user = await updateUser();
80 |
81 | if (user.id) {
82 | setUserIsAuthenticated(true);
83 | }
84 | }
85 | };
86 |
87 | return (
88 |
89 | You are not logged in.
90 | setEmail(text)}
92 | value={email}
93 | placeholder='Email'
94 | />
95 | setPassword(text)}
97 | value={password}
98 | secureTextEntry
99 | placeholder='Password'
100 | />
101 |
102 | Login
103 |
104 |
105 | );
106 | }
107 | ```
108 |
109 | ## Example App
110 |
111 | You can find the example app [here](https://github.com/Tschucki/react-native-laravel-sanctum-example-app).
112 |
113 | ## Documentation
114 |
115 | You can find the documentation [here](https://github.com/Tschucki/react-native-laravel-sanctum/wiki).
116 |
117 | ## Contributing
118 |
119 | See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
120 |
121 | ## License
122 |
123 | MIT
124 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['module:metro-react-native-babel-preset'],
3 | };
4 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | web-build
2 |
--------------------------------------------------------------------------------
/example/App.js:
--------------------------------------------------------------------------------
1 | export { default } from './src/App';
2 |
--------------------------------------------------------------------------------
/example/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "react-native-laravel-sanctum-example",
4 | "slug": "react-native-laravel-sanctum-example",
5 | "version": "1.0.0",
6 | "orientation": "portrait",
7 | "icon": "./assets/icon.png",
8 | "userInterfaceStyle": "light",
9 | "splash": {
10 | "image": "./assets/splash.png",
11 | "resizeMode": "contain",
12 | "backgroundColor": "#ffffff"
13 | },
14 | "assetBundlePatterns": [
15 | "**/*"
16 | ],
17 | "ios": {
18 | "supportsTablet": true
19 | },
20 | "android": {
21 | "adaptiveIcon": {
22 | "foregroundImage": "./assets/adaptive-icon.png",
23 | "backgroundColor": "#ffffff"
24 | }
25 | },
26 | "web": {
27 | "favicon": "./assets/favicon.png"
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/example/assets/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tschucki/react-native-laravel-sanctum/674df4d8075f9fc7c223b8040d550ae9503e33de/example/assets/adaptive-icon.png
--------------------------------------------------------------------------------
/example/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tschucki/react-native-laravel-sanctum/674df4d8075f9fc7c223b8040d550ae9503e33de/example/assets/favicon.png
--------------------------------------------------------------------------------
/example/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tschucki/react-native-laravel-sanctum/674df4d8075f9fc7c223b8040d550ae9503e33de/example/assets/icon.png
--------------------------------------------------------------------------------
/example/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tschucki/react-native-laravel-sanctum/674df4d8075f9fc7c223b8040d550ae9503e33de/example/assets/splash.png
--------------------------------------------------------------------------------
/example/babel.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const pak = require('../package.json');
3 |
4 | module.exports = function (api) {
5 | api.cache(true);
6 |
7 | return {
8 | presets: ['babel-preset-expo'],
9 | plugins: [
10 | [
11 | 'module-resolver',
12 | {
13 | extensions: ['.tsx', '.ts', '.js', '.json'],
14 | alias: {
15 | // For development, we want to alias the library to the source
16 | [pak.name]: path.join(__dirname, '..', pak.source),
17 | },
18 | },
19 | ],
20 | ],
21 | };
22 | };
23 |
--------------------------------------------------------------------------------
/example/metro.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const escape = require('escape-string-regexp');
3 | const { getDefaultConfig } = require('@expo/metro-config');
4 | const exclusionList = require('metro-config/src/defaults/exclusionList');
5 | const pak = require('../package.json');
6 |
7 | const root = path.resolve(__dirname, '..');
8 | const modules = Object.keys({ ...pak.peerDependencies });
9 |
10 | const defaultConfig = getDefaultConfig(__dirname);
11 |
12 | /**
13 | * Metro configuration
14 | * https://facebook.github.io/metro/docs/configuration
15 | *
16 | * @type {import('metro-config').MetroConfig}
17 | */
18 | const config = {
19 | ...defaultConfig,
20 |
21 | projectRoot: __dirname,
22 | watchFolders: [root],
23 |
24 | // We need to make sure that only one version is loaded for peerDependencies
25 | // So we block them at the root, and alias them to the versions in example's node_modules
26 | resolver: {
27 | ...defaultConfig.resolver,
28 |
29 | blacklistRE: exclusionList(
30 | modules.map(
31 | (m) =>
32 | new RegExp(`^${escape(path.join(root, 'node_modules', m))}\\/.*$`)
33 | )
34 | ),
35 |
36 | extraNodeModules: modules.reduce((acc, name) => {
37 | acc[name] = path.join(__dirname, 'node_modules', name);
38 | return acc;
39 | }, {}),
40 | },
41 | };
42 |
43 | module.exports = config;
44 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "1.0.0",
4 | "main": "node_modules/expo/AppEntry.js",
5 | "scripts": {
6 | "start": "expo start",
7 | "android": "expo start --android",
8 | "ios": "expo start --ios",
9 | "web": "expo start --web"
10 | },
11 | "dependencies": {
12 | "expo": "~49.0.7",
13 | "expo-secure-store": "~12.3.1",
14 | "expo-status-bar": "~1.6.0",
15 | "react": "18.2.0",
16 | "react-dom": "18.2.0",
17 | "react-native": "0.72.4",
18 | "react-native-laravel-sanctum": "^0.3.0",
19 | "react-native-web": "~0.19.6"
20 | },
21 | "devDependencies": {
22 | "@babel/core": "^7.20.0",
23 | "@expo/webpack-config": "^19.0.0",
24 | "babel-loader": "^8.1.0",
25 | "babel-plugin-module-resolver": "^5.0.0"
26 | },
27 | "private": true
28 | }
29 |
--------------------------------------------------------------------------------
/example/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
3 | import { AuthProvider, useAuth } from 'react-native-laravel-sanctum';
4 |
5 | function Login() {
6 | const {
7 | login,
8 | getToken,
9 | updateUser,
10 | setUserIsAuthenticated,
11 | isAuthenticated,
12 | currentUser,
13 | } = useAuth();
14 | const [email, setEmail] = useState('');
15 | const [password, setPassword] = useState('');
16 |
17 | useEffect(() => {
18 | console.log(currentUser);
19 | }, [currentUser]);
20 | useEffect(() => {
21 | console.log(isAuthenticated);
22 | }, [isAuthenticated]);
23 |
24 | const handleLogin = async () => {
25 | console.log(await getToken());
26 | const tokenObtained = await login(email, password, 'Test-Device');
27 | console.log(tokenObtained);
28 | if (tokenObtained) {
29 | const user = await updateUser();
30 | if (user && user.id) {
31 | setUserIsAuthenticated(true);
32 | }
33 | }
34 | };
35 |
36 | return (
37 |
38 | You are not logged in.
39 | setEmail(text)}
42 | value={email}
43 | placeholder="Email"
44 | />
45 | setPassword(text)}
48 | value={password}
49 | secureTextEntry
50 | placeholder="Password"
51 | />
52 |
53 | Login
54 |
55 |
56 | );
57 | }
58 |
59 | function Logout() {
60 | const { logout, setUserIsAuthenticated, updateUser, currentUser } = useAuth();
61 |
62 | const handleLogout = async () => {
63 | const isLoggedOut = await logout();
64 | if (isLoggedOut) {
65 | setUserIsAuthenticated(false);
66 | await updateUser();
67 | }
68 | };
69 |
70 | return (
71 |
72 |
73 | You are logged in as {currentUser ? currentUser.name : ''}
74 |
75 |
76 | Logout
77 |
78 |
79 | );
80 | }
81 |
82 | function Example() {
83 | const { isAuthenticated } = useAuth();
84 | return (
85 | {isAuthenticated ? : }
86 | );
87 | }
88 |
89 | export default function App() {
90 | const config = {
91 | loginUrl: 'http://127.0.0.1:8000/api/sanctum/token',
92 | logoutUrl: 'http://127.0.0.1:8000/api/logout',
93 | userUrl: 'http://127.0.0.1:8000/api/user',
94 | };
95 |
96 | return (
97 |
98 |
99 |
100 |
101 |
102 | );
103 | }
104 |
105 | const styles = StyleSheet.create({
106 | container: {
107 | flex: 1,
108 | backgroundColor: '#fff',
109 | alignItems: 'center',
110 | justifyContent: 'center',
111 | },
112 | button: {
113 | backgroundColor: '#f59e0b',
114 | padding: 12,
115 | minWidth: '80%',
116 | borderRadius: 5,
117 | alignItems: 'center',
118 | },
119 | buttonText: {
120 | fontWeight: '800',
121 | letterSpacing: 0.5,
122 | lineHeight: 20,
123 | color: '#fff',
124 | },
125 | textInput: {
126 | height: 40,
127 | borderColor: 'gray',
128 | borderWidth: 1,
129 | minWidth: '80%',
130 | padding: 10,
131 | borderRadius: 5,
132 | marginBottom: 10,
133 | },
134 | caption: {
135 | marginBottom: 10,
136 | fontWeight: '800',
137 | letterSpacing: 0.5,
138 | lineHeight: 20,
139 | color: '#000',
140 | textAlign: 'center',
141 | },
142 | view: {
143 | padding: 15,
144 | alignItems: 'center',
145 | },
146 | });
147 |
--------------------------------------------------------------------------------
/example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig",
3 | "compilerOptions": {
4 | // Avoid expo-cli auto-generating a tsconfig
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/example/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const createExpoWebpackConfigAsync = require('@expo/webpack-config');
3 | const { resolver } = require('./metro.config');
4 |
5 | const root = path.resolve(__dirname, '..');
6 | const node_modules = path.join(__dirname, 'node_modules');
7 |
8 | module.exports = async function (env, argv) {
9 | const config = await createExpoWebpackConfigAsync(env, argv);
10 |
11 | config.module.rules.push({
12 | test: /\.(js|jsx|ts|tsx)$/,
13 | include: path.resolve(root, 'src'),
14 | use: 'babel-loader',
15 | });
16 |
17 | // We need to make sure that only one version is loaded for peerDependencies
18 | // So we alias them to the versions in example's node_modules
19 | Object.assign(config.resolve.alias, {
20 | ...resolver.extraNodeModules,
21 | 'react-native-web': path.join(node_modules, 'react-native-web'),
22 | });
23 |
24 | return config;
25 | };
26 |
--------------------------------------------------------------------------------
/lefthook.yml:
--------------------------------------------------------------------------------
1 | pre-commit:
2 | parallel: true
3 | commands:
4 | lint:
5 | files: git diff --name-only @{push}
6 | glob: "*.{js,ts,jsx,tsx}"
7 | run: npx eslint {files}
8 | types:
9 | files: git diff --name-only @{push}
10 | glob: "*.{js,ts, jsx, tsx}"
11 | run: npx tsc --noEmit
12 | commit-msg:
13 | parallel: true
14 | commands:
15 | commitlint:
16 | run: npx commitlint --edit
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-laravel-sanctum",
3 | "version": "0.3.0",
4 | "description": "React Native auth package for Laravel Sanctum integration",
5 | "main": "lib/commonjs/index",
6 | "module": "lib/module/index",
7 | "types": "lib/typescript/index.d.ts",
8 | "react-native": "src/index",
9 | "source": "src/index",
10 | "files": [
11 | "src",
12 | "lib",
13 | "android",
14 | "ios",
15 | "cpp",
16 | "*.podspec",
17 | "!lib/typescript/example",
18 | "!ios/build",
19 | "!android/build",
20 | "!android/gradle",
21 | "!android/gradlew",
22 | "!android/gradlew.bat",
23 | "!android/local.properties",
24 | "!**/__tests__",
25 | "!**/__fixtures__",
26 | "!**/__mocks__",
27 | "!**/.*"
28 | ],
29 | "scripts": {
30 | "test": "jest",
31 | "typecheck": "tsc --noEmit",
32 | "lint": "eslint \"**/*.{js,ts,tsx}\"",
33 | "prepack": "bob build",
34 | "example": "yarn --cwd example",
35 | "release": "release-it"
36 | },
37 | "keywords": [
38 | "react-native",
39 | "ios",
40 | "android"
41 | ],
42 | "repository": "https://github.com/Tschucki/react-native-laravel-sanctum",
43 | "author": "Marcel Wagner (https://www.marcelwagner.dev)",
44 | "license": "MIT",
45 | "bugs": {
46 | "url": "https://github.com/Tschucki/react-native-laravel-sanctum/issues"
47 | },
48 | "homepage": "https://github.com/Tschucki/react-native-laravel-sanctum#readme",
49 | "publishConfig": {
50 | "registry": "https://registry.npmjs.org/"
51 | },
52 | "devDependencies": {
53 | "@commitlint/config-conventional": "^17.0.2",
54 | "@evilmartians/lefthook": "^1.2.2",
55 | "@react-native-community/eslint-config": "^3.0.2",
56 | "@release-it/conventional-changelog": "^5.0.0",
57 | "@types/jest": "^28.1.2",
58 | "@types/react": "~17.0.21",
59 | "@types/react-native": "0.70.0",
60 | "commitlint": "^17.0.2",
61 | "del-cli": "^5.0.0",
62 | "eslint": "^8.4.1",
63 | "eslint-config-prettier": "^8.5.0",
64 | "eslint-plugin-prettier": "^4.0.0",
65 | "jest": "^28.1.1",
66 | "pod-install": "^0.1.0",
67 | "prettier": "^2.0.5",
68 | "react": "18.2.0",
69 | "react-native": "0.72.3",
70 | "react-native-builder-bob": "^0.21.3",
71 | "release-it": "^15.0.0",
72 | "typescript": "^5.0.2"
73 | },
74 | "resolutions": {
75 | "@types/react": "17.0.21"
76 | },
77 | "peerDependencies": {
78 | "react": "*",
79 | "react-native": "*"
80 | },
81 | "engines": {
82 | "node": ">= 16.0.0"
83 | },
84 | "jest": {
85 | "preset": "react-native",
86 | "modulePathIgnorePatterns": [
87 | "/example/node_modules",
88 | "/lib/"
89 | ]
90 | },
91 | "commitlint": {
92 | "extends": [
93 | "@commitlint/config-conventional"
94 | ]
95 | },
96 | "release-it": {
97 | "git": {
98 | "commitMessage": "chore: release ${version}",
99 | "tagName": "v${version}"
100 | },
101 | "npm": {
102 | "publish": true
103 | },
104 | "github": {
105 | "release": true
106 | },
107 | "plugins": {
108 | "@release-it/conventional-changelog": {
109 | "preset": "angular"
110 | }
111 | }
112 | },
113 | "eslintConfig": {
114 | "root": true,
115 | "extends": [
116 | "@react-native-community",
117 | "prettier"
118 | ],
119 | "rules": {
120 | "prettier/prettier": [
121 | "error",
122 | {
123 | "quoteProps": "consistent",
124 | "singleQuote": true,
125 | "tabWidth": 2,
126 | "trailingComma": "es5",
127 | "useTabs": false
128 | }
129 | ]
130 | }
131 | },
132 | "eslintIgnore": [
133 | "node_modules/",
134 | "lib/"
135 | ],
136 | "prettier": {
137 | "quoteProps": "consistent",
138 | "singleQuote": true,
139 | "tabWidth": 2,
140 | "trailingComma": "es5",
141 | "useTabs": false
142 | },
143 | "react-native-builder-bob": {
144 | "source": "src",
145 | "output": "lib",
146 | "targets": [
147 | "commonjs",
148 | "module",
149 | [
150 | "typescript",
151 | {
152 | "project": "tsconfig.build.json"
153 | }
154 | ]
155 | ]
156 | },
157 | "dependencies": {
158 | "@react-native-async-storage/async-storage": "^1.21.0",
159 | "expo-secure-store": "^12.3.1"
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/scripts/bootstrap.js:
--------------------------------------------------------------------------------
1 | const os = require('os');
2 | const path = require('path');
3 | const child_process = require('child_process');
4 |
5 | const root = path.resolve(__dirname, '..');
6 | const args = process.argv.slice(2);
7 | const options = {
8 | cwd: process.cwd(),
9 | env: process.env,
10 | stdio: 'inherit',
11 | encoding: 'utf-8',
12 | };
13 |
14 | if (os.type() === 'Windows_NT') {
15 | options.shell = true;
16 | }
17 |
18 | let result;
19 |
20 | if (process.cwd() !== root || args.length) {
21 | // We're not in the root of the project, or additional arguments were passed
22 | // In this case, forward the command to `yarn`
23 | result = child_process.spawnSync('yarn', args, options);
24 | } else {
25 | // If `yarn` is run without arguments, perform bootstrap
26 | result = child_process.spawnSync('yarn', ['bootstrap'], options);
27 | }
28 |
29 | process.exitCode = result.status;
30 |
--------------------------------------------------------------------------------
/src/__tests__/index.test.tsx:
--------------------------------------------------------------------------------
1 | it.todo('write a test');
2 |
--------------------------------------------------------------------------------
/src/contexts/AuthContext.ts:
--------------------------------------------------------------------------------
1 | import { createContext } from 'react';
2 | import type { AuthContextType } from '../interfaces/AuthContextType';
3 |
4 | const AuthContext = createContext(undefined);
5 |
6 | export default AuthContext;
7 |
--------------------------------------------------------------------------------
/src/contexts/AuthProvider.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import type { AuthConfig } from '../interfaces/AuthConfig';
3 | import AuthContext from './AuthContext';
4 | import type { AuthProviderProps } from '../interfaces/AuthProviderProps';
5 | import type { User } from '../interfaces/User';
6 | import AuthService from '../services/AuthService';
7 | import TokenStorage from '../utils/TokenStorage';
8 |
9 | const AuthProvider: React.FC = ({ config, children }) => {
10 | const [currentConfig, setCurrentConfig] = useState(config);
11 | const [currentUser, setCurrentUser] = useState(null);
12 | const [isAuthenticated, setIsAuthenticated] = useState(false);
13 | const [authService, setAuthService] = useState();
14 |
15 | useEffect(() => {
16 | if (authService) {
17 | checkIfAuthenticated();
18 | }
19 | // eslint-disable-next-line react-hooks/exhaustive-deps
20 | }, [authService]);
21 |
22 | useEffect(() => {
23 | return setAuthService(new AuthService(currentConfig));
24 | }, [currentConfig]);
25 |
26 | useEffect(() => {
27 | setCurrentConfig(config);
28 | }, [config]);
29 |
30 | const checkIfAuthenticated = async () => {
31 | if (authService === undefined) throw new Error('AuthService is undefined');
32 | try {
33 | await updateUser().then((user) => {
34 | if (user === null) {
35 | setUserIsAuthenticated(false);
36 | } else if (user.id) {
37 | setUserIsAuthenticated(true);
38 | }
39 | });
40 | } catch (error) {
41 | console.error('Error while checking token:', error);
42 | throw error;
43 | }
44 | };
45 |
46 | const login = async (email: string, password: string, deviceName: string) => {
47 | if (authService === undefined) throw new Error('AuthService is undefined');
48 | try {
49 | return await authService.login(email, password, deviceName);
50 | } catch (error) {
51 | console.error('Error during login:', error);
52 | throw error;
53 | }
54 | };
55 |
56 | const updateUser = async (): Promise => {
57 | if (authService === undefined) throw new Error('AuthService is undefined');
58 | try {
59 | return await authService.getUser().then((fetchedUser) => {
60 | setCurrentUser(fetchedUser);
61 | return fetchedUser;
62 | });
63 | } catch (error) {
64 | console.error('Error while fetching user:', error);
65 | throw error;
66 | }
67 | };
68 |
69 | const setUserIsAuthenticated = (userIsAuthenticated: boolean): void => {
70 | setIsAuthenticated(userIsAuthenticated);
71 | };
72 |
73 | const getToken = async () => {
74 | return await TokenStorage.getToken()
75 | .then((apiToken) => {
76 | return apiToken;
77 | })
78 | .catch((error) => {
79 | console.error('Error while getting token:', error);
80 | throw error;
81 | })
82 | .finally(() => {
83 | return null;
84 | });
85 | };
86 |
87 | const logout = async () => {
88 | if (authService === undefined) throw new Error('AuthService is undefined');
89 | try {
90 | return await authService.logout();
91 | } catch (error) {
92 | console.error('Error during logout:', error);
93 | throw error;
94 | }
95 | };
96 |
97 | return (
98 |
109 | {children}
110 |
111 | );
112 | };
113 |
114 | export { AuthProvider };
115 |
--------------------------------------------------------------------------------
/src/hooks/useAuth.ts:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react';
2 | import AuthContext from '../contexts/AuthContext';
3 |
4 | const useAuth = () => {
5 | const context = useContext(AuthContext);
6 | if (!context) {
7 | throw new Error('useAuth muss innerhalb von AuthProvider verwendet werden');
8 | }
9 | return context;
10 | };
11 |
12 | export { useAuth };
13 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | export { AuthProvider } from './contexts/AuthProvider';
2 | export type { User } from './interfaces/User';
3 | export type { AuthConfig } from './interfaces/AuthConfig';
4 | export { useAuth } from './hooks/useAuth';
5 |
--------------------------------------------------------------------------------
/src/interfaces/AuthConfig.ts:
--------------------------------------------------------------------------------
1 | export interface AuthConfig {
2 | loginUrl: string;
3 | logoutUrl: string;
4 | userUrl: string;
5 | csrfTokenUrl?: string | null;
6 | }
7 |
--------------------------------------------------------------------------------
/src/interfaces/AuthContextType.ts:
--------------------------------------------------------------------------------
1 | import type { User } from './User';
2 |
3 | export interface AuthContextType {
4 | getToken: () => Promise;
5 | currentUser: User | null;
6 | updateUser: () => Promise;
7 | isAuthenticated: boolean;
8 | setUserIsAuthenticated: (userIsAuthenticated: boolean) => void;
9 | login: (
10 | email: string,
11 | password: string,
12 | deviceName: string
13 | ) => Promise;
14 | logout: () => Promise;
15 | }
16 |
--------------------------------------------------------------------------------
/src/interfaces/AuthProviderProps.ts:
--------------------------------------------------------------------------------
1 | import { type AuthConfig } from './AuthConfig';
2 | import { type ReactNode } from 'react';
3 |
4 | export interface AuthProviderProps {
5 | config: AuthConfig;
6 | children: ReactNode;
7 | }
8 |
--------------------------------------------------------------------------------
/src/interfaces/AuthResponse.ts:
--------------------------------------------------------------------------------
1 | import type { User } from './User';
2 |
3 | export interface AuthResponse {
4 | token: string;
5 | user: User;
6 | }
7 |
--------------------------------------------------------------------------------
/src/interfaces/User.ts:
--------------------------------------------------------------------------------
1 | export interface User {
2 | id: number;
3 | name: string;
4 | email: string;
5 | }
6 |
--------------------------------------------------------------------------------
/src/services/AuthService.ts:
--------------------------------------------------------------------------------
1 | import TokenStorage from '../utils/TokenStorage';
2 | import type { AuthConfig, User } from 'react-native-laravel-sanctum';
3 |
4 | class AuthService {
5 | private readonly config: AuthConfig | null;
6 | private csrfToken: string | null = null; // CSRF-Token speichern
7 |
8 | constructor(authConfig: AuthConfig) {
9 | if (authConfig === null) {
10 | throw new Error('AuthConfig is null');
11 | }
12 | this.config = authConfig;
13 | }
14 |
15 | private async handleResponse(response: Response): Promise {
16 | if (!response.ok) {
17 | throw new Error('Request was not successful');
18 | }
19 | }
20 |
21 | private async fetchCSRFToken() {
22 | try {
23 | if (!this.config || !this.config.csrfTokenUrl) {
24 | return;
25 | }
26 |
27 | const response = await fetch(this.config.csrfTokenUrl, {
28 | method: 'GET',
29 | headers: {
30 | 'Content-Type': 'application/json',
31 | },
32 | });
33 |
34 | await this.handleResponse(response);
35 |
36 | // Extrahieren des CSRF-Tokens aus dem Set-Cookie-Header
37 | const setCookieHeader = response.headers.get('set-cookie');
38 | if (setCookieHeader) {
39 | const csrfTokenMatch = setCookieHeader.match(/XSRF-TOKEN=([^;]*)/);
40 | if (csrfTokenMatch) {
41 | this.csrfToken = csrfTokenMatch[1] ?? null;
42 | }
43 | }
44 | } catch (error) {
45 | console.error('Error while fetching CSRF token:', error);
46 | throw error;
47 | }
48 | }
49 |
50 | private async getRequestHeaders() {
51 | const headers: Record = {
52 | 'Content-Type': 'application/json',
53 | };
54 |
55 | if (this.csrfToken) {
56 | headers['X-XSRF-TOKEN'] = this.csrfToken;
57 | }
58 |
59 | const currentToken = await TokenStorage.getToken();
60 | if (currentToken) {
61 | headers.Authorization = `Bearer ${currentToken}`;
62 | }
63 |
64 | return headers;
65 | }
66 |
67 | async login(
68 | email: string,
69 | password: string,
70 | deviceName: string
71 | ): Promise {
72 | try {
73 | if (!this.config) {
74 | throw new Error('Authentication configuration is missing');
75 | }
76 |
77 | if (this.config.csrfTokenUrl) {
78 | await this.fetchCSRFToken();
79 | }
80 |
81 | const response = await fetch(this.config.loginUrl, {
82 | method: 'POST',
83 | headers: await this.getRequestHeaders(),
84 | body: JSON.stringify({
85 | email,
86 | password,
87 | deviceName,
88 | }),
89 | });
90 |
91 | await this.handleResponse(response);
92 |
93 | const data = await response.json();
94 | const { token } = data;
95 |
96 | if (token) {
97 | await TokenStorage.saveToken(token);
98 | return true;
99 | } else {
100 | return false;
101 | }
102 | } catch (error) {
103 | console.error('Error during login:', error);
104 | throw error;
105 | }
106 | }
107 |
108 | async logout(): Promise {
109 | try {
110 | if (!this.config) {
111 | throw new Error('Authentication configuration is missing');
112 | }
113 |
114 | if (this.config.csrfTokenUrl) {
115 | await this.fetchCSRFToken();
116 | }
117 |
118 | const currentToken = await TokenStorage.getToken();
119 |
120 | if (currentToken === null) {
121 | return true;
122 | }
123 |
124 | const response = await fetch(this.config.logoutUrl, {
125 | method: 'POST',
126 | headers: await this.getRequestHeaders(),
127 | });
128 |
129 | await this.handleResponse(response);
130 | await TokenStorage.removeToken();
131 | return true;
132 | } catch (error) {
133 | console.error('Error during logout:', error);
134 | throw error;
135 | }
136 | }
137 |
138 | async getUser(): Promise {
139 | try {
140 | if (!this.config) {
141 | throw new Error('Authentication configuration is missing');
142 | }
143 |
144 | const currentToken = await TokenStorage.getToken();
145 |
146 | if (currentToken === null) {
147 | return null;
148 | }
149 |
150 | if (this.config.csrfTokenUrl) {
151 | await this.fetchCSRFToken();
152 | }
153 |
154 | const response = await fetch(this.config.userUrl, {
155 | method: 'GET',
156 | headers: await this.getRequestHeaders(),
157 | });
158 |
159 | await this.handleResponse(response);
160 |
161 | const user = await response.json();
162 |
163 | if (user) {
164 | this.csrfToken = null;
165 | return user;
166 | } else {
167 | return null;
168 | }
169 | } catch (error) {
170 | console.error('Error while fetching user:', error);
171 | throw error;
172 | }
173 | }
174 | }
175 |
176 | export default AuthService;
177 |
--------------------------------------------------------------------------------
/src/utils/TokenStorage.native.ts:
--------------------------------------------------------------------------------
1 | import * as SecureStore from 'expo-secure-store';
2 |
3 | class TokenStorage {
4 | private static readonly TOKEN_KEY = 'authToken';
5 |
6 | static async saveToken(token: string): Promise {
7 | try {
8 | await SecureStore.setItemAsync(TokenStorage.TOKEN_KEY, token);
9 | } catch (error) {
10 | console.error('Error while saving the encrypted token:', error);
11 | throw error;
12 | }
13 | }
14 |
15 | static async getToken(): Promise {
16 | try {
17 | const encryptedToken = await SecureStore.getItemAsync(
18 | TokenStorage.TOKEN_KEY
19 | );
20 | return encryptedToken || null;
21 | } catch (error) {
22 | console.error('Error while retrieving the encrypted token:', error);
23 | throw error;
24 | }
25 | }
26 |
27 | static async removeToken(): Promise {
28 | try {
29 | await SecureStore.deleteItemAsync(TokenStorage.TOKEN_KEY);
30 | } catch (error) {
31 | console.error('Error while removing the encrypted token:', error);
32 | throw error;
33 | }
34 | }
35 | }
36 |
37 | export default TokenStorage;
38 |
--------------------------------------------------------------------------------
/src/utils/TokenStorage.ts:
--------------------------------------------------------------------------------
1 | import AsyncStorage from '@react-native-async-storage/async-storage';
2 |
3 | class TokenStorage {
4 | private static readonly TOKEN_KEY = 'authToken';
5 |
6 | static async saveToken(token: string): Promise {
7 | try {
8 | await AsyncStorage.setItem(TokenStorage.TOKEN_KEY, token);
9 | } catch (error) {
10 | console.error('Error while saving the encrypted token:', error);
11 | throw error;
12 | }
13 | }
14 |
15 | static async getToken(): Promise {
16 | try {
17 | const localToken = await AsyncStorage.getItem(TokenStorage.TOKEN_KEY);
18 | return localToken || null;
19 | } catch (error) {
20 | console.error('Error while retrieving the encrypted token:', error);
21 | throw error;
22 | }
23 | }
24 |
25 | static async removeToken(): Promise {
26 | try {
27 | await AsyncStorage.removeItem(TokenStorage.TOKEN_KEY);
28 | } catch (error) {
29 | console.error('Error while removing the encrypted token:', error);
30 | throw error;
31 | }
32 | }
33 | }
34 |
35 | export default TokenStorage;
36 |
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "extends": "./tsconfig",
4 | "exclude": ["example"]
5 | }
6 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "./",
4 | "paths": {
5 | "react-native-laravel-sanctum": ["./src/index"]
6 | },
7 | "allowUnreachableCode": false,
8 | "allowUnusedLabels": false,
9 | "esModuleInterop": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "jsx": "react",
12 | "lib": ["esnext"],
13 | "module": "esnext",
14 | "moduleResolution": "node",
15 | "noFallthroughCasesInSwitch": true,
16 | "noImplicitReturns": true,
17 | "noImplicitUseStrict": false,
18 | "noStrictGenericChecks": false,
19 | "noUncheckedIndexedAccess": true,
20 | "noUnusedLocals": true,
21 | "noUnusedParameters": true,
22 | "resolveJsonModule": true,
23 | "skipLibCheck": true,
24 | "strict": true,
25 | "target": "esnext",
26 | "verbatimModuleSyntax": true
27 | }
28 | }
29 |
--------------------------------------------------------------------------------