GithubHelp home page GithubHelp logo

koole / react-sanctum Goto Github PK

View Code? Open in Web Editor NEW
140.0 6.0 26.0 2.48 MB

Easily hook up your React app to Laravel Sanctum and Laravel Fortify

License: MIT License

TypeScript 14.12% JavaScript 1.94% PHP 69.55% Blade 13.14% HTML 1.25%
react-components react sanctum laravel-sanctum authentication laravel-fortify two-factor-authentication

react-sanctum's Introduction

Logo Laravel Sanctum

Build Status npm npm npm bundle size GitHub

Introduction

React Sanctum package provides an easy way to authenticate your React application with Laravel Sanctum.

  • Easily hook up your React app to Laravel Sanctum
  • Works with both hooks and class components
  • Built in support for two factor authentication with Laravel Fortify
  • Just one dependency: axios

Usage

Install from NPM

npm i react-sanctum

Wrap your application in a <Sanctum> component

Example

import React from "react";

import { Sanctum } from "react-sanctum";

const sanctumConfig = {
  apiUrl: "http://foobar.test",
  csrfCookieRoute: "sanctum/csrf-cookie",
  signInRoute: "login",
  signOutRoute: "logout",
  userObjectRoute: "user",
};

const App = () => (
  <div className="my-application">
    <Sanctum config={sanctumConfig}>/* Your application code */</Sanctum>
  </div>
);

You can then use the useSanctum() hook to get authentication status, user data and sanctum related methods in any component.

import React from "react";
import { useSanctum } from "react-sanctum";

const LoginButton = () => {
  const { authenticated, user, signIn } = useSanctum();

  const handleLogin = () => {
    const email = "[email protected]";
    const password = "example";
    const remember = true;

    signIn(email, password, remember)
      .then(() => window.alert("Signed in!"))
      .catch(() => window.alert("Incorrect email or password"));
  };

  if (authenticated === true) {
    return <h1>Welcome, {user.name}</h1>;
  } else {
    return <button onClick={handleLogin}>Sign in</button>;
  }
};

export default LoginButton;

Or use the withSanctum() higher-order component to get these same values.

import React from "react";
import { withSanctum } from "react-sanctum";

const LoginButton = ({ authenticated, user, signIn }) => {
    ...
};

export default withSanctum(LoginButton);

You can also directly consume the Sanctum context by importing SanctumContext.

The useSanctum hook and the withSanctum HOC give you access to the SanctumContext, which contains the following data and methods:

Description
user Object your API returns with user data
authenticated Boolean, or null if authentication has not yet been checked
signIn() Accepts (email, password, remember?), returns a promise, resolves with {twoFactor: boolean, signedIn: boolean, user: {}}.
signOut() Returns a promise
setUser() Accepts (user, authenticated?), allows you to manually set the user object and optionally its authentication status (boolean).
twoFactorChallenge() Accepts (code, recovery?), returns a promise, resolves with the user object.
checkAuthentication() Returns the authentication status. If it's null, it will ask the server and update authenticated.

Setup

All URLS in the config are required. These need to be created in your Laravel app.

const sanctumConfig = {
  // Your application URL
  apiUrl: "http://foobar.test",
  // The following settings are URLS that need to be created in your Laravel application
  // The URL sanctum uses for the csrf cookie
  csrfCookieRoute: "sanctum/csrf-cookie",
  // {email: string, password: string, remember: true | null} get POSTed to here
  signInRoute: "api/login",
  // A POST request is sent to this route to sign the user out
  signOutRoute: "api/logout",
  // Used (GET) for checking if the user is signed in (so this should be protected)
  // The returned object will be avaiable as `user` in the React components.
  userObjectRoute: "api/user",
  // The URL where the OTAP token or recovery code will be sent to (optional).
  // Only needed if you want to use two factor authentication.
  twoFactorChallengeRoute: "two-factor-challenge",
  // An axios instance to be used by react-sanctum (optional). Useful if you for example need to add custom interceptors.
  axiosInstance: AxiosInstance,
  // Optional key used for the username POSTed to Laravel, defaults to "email". 
  usernameKey: "email";
};

react-sanctum automatically checks if the user is signed in when the the <Sanctum> component gets mounted. If you don't want this, and want to manually use the checkAuthentication function later, set checkOnInit to false like so:

<Sanctum config={sanctumConfig} checkOnInit={false}>

Handling registration

Methods for signIn and signOut are provided by this library. Registration is not included as there seem to be many ways people handle registration flows.

If you want to sign in your user after registration, there's an easy way to do this. First, make sure the endpoint you post the registration data to signs in the user (Auth::guard()->login(...)) and have it return the user object to the front-end.

In your front-end you can then pass this user object into the setUser() function, et voilà, your new user has been signed in.

For example:

axios
    .post(`${API_URL}/register`, data)
    .then(function (response) {
        const user = response.data;
        setUser(user); // The react-sanctum setUser function
        ...
    })
    .catch(function (error) {
        ...
    });

Two factor authentication

This package supports two factor authentication using Laravel Fortify out of the box.

  1. Install Laravel Fortify using the following instructions https://laravel.com/docs/8.x/fortify#installation

  2. Add the TwoFactorAuthenticable trait to the User model https://laravel.com/docs/8.x/fortify#two-factor-authentication

  3. Make sure the two-factor-challenge route is included in the config/cors.php file.

Example for implementation:

import React, { useState } from "react";
import { useSanctum } from "react-sanctum";

const Login = () => {
  const [showTwoFactorForm, setShowTwoFactorForm] = useState(false);
  const [code, setCode] = useState("");
  const [recoveryCode, setRecoveryCode] = useState("");
  const { authenticated, user, signIn, twoFactorChallenge } = useSanctum();

  const handleLogin = () => {
    const email = "[email protected]";
    const password = "password";
    const remember = true;

    signIn(email, password, remember)
      .then(({ twoFactor }) => {
        if (twoFactor) {
          setShowTwoFactorForm(true);
          return;
        }

        window.alert("Signed in without token!");
      })
      .catch(() => window.alert("Incorrect email or password"));
  };

  const handleTwoFactorChallenge = (recovery = false) => {
    twoFactorChallenge(recovery ? recoveryCode : code, recovery)
      .then(() => window.alert("Signed in with token!"))
      .catch(() => window.alert("Incorrect token"));
  };

  if (authenticated === true) {
    return <h1>Welcome, {user.name}</h1>;
  } else {
    if (showTwoFactorForm) {
      return (
        <div>
          <input
            type="text"
            onInput={(event) => setCode(event.currentTarget.value)}
          />
          <button onClick={() => handleTwoFactorChallenge()}>
            Sign in using OTAP-token
          </button>
          <hr />
          <input
            type="text"
            onInput={(event) => setRecoveryCode(event.currentTarget.value)}
          />
          <button onClick={() => handleTwoFactorChallenge(true)}>
            Sign in using recovery token
          </button>
        </div>
      );
    }

    return <button onClick={handleLogin}>Sign in</button>;
  }
};

export default Login;

Axios

Quick tip for people using axios: react-sanctum uses Axios for making requests to your server. If your project is also using axios, make sure to set axios.defaults.withCredentials = true;. That way axios will authenticate your requests to the server properly.

react-sanctum's People

Contributors

ali-wells avatar d0utone avatar dependabot[bot] avatar dmason30 avatar gmoigneu avatar harrisonratcliffe avatar koole avatar verwer avatar wildego avatar yoieh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

react-sanctum's Issues

User is always returned : false

Hi !
The backend implementation works fine as the login route does output in the console the correct answer from the server with the user variable
But when I use signIn, it does give success but in 'then' I have User:false

any idea ?

User Type defenition

Hey mate just picked up this library to fool around, noticed there's no way to set my user type as anything besides what is defined on the library, is there any chance we could add something for it to be useSanctum<TypeDef>() giving the user whatever the type TypeDef is?

Session Expired Response Handling

Right now, if you leave your browser window open too long and then attempt to make a request to your laravel application from your React Application, the token times out and the module doesn't automatically refresh the token, there is an error thrown CSRF Token mismatch.

Possibly include a configuration option that automatically enables an axios response interceptor that runs a function from the configuration when status code 419: Session Expired is returned?

As explained here: https://laracasts.com/discuss/channels/general-discussion/detecting-expired-session-with-sanctumspa
This leaves it open for the user to either write a function to reload the window, or reroute the user to re-authenticate, or if they want to try to refresh the session using the csrf method that is already provided.

onAuthenticated and getUser callbacks

I'm using, in a project, react-sanctum and it feels great. In the meanwhile, callbacks like onAuthenticated() and getUser() on Sanctum and withSanctum could be of great use.

<Sanctum
    checkOnInit
    config={} 
    onAuthenticated={(user,....) => {}} 
>
    <SubAppComponents />
</Sanctum>
const SubAppComponents = ({user, getUser, authenticated } ) => {
    // refresh user object
    if (condition) getUser() 
    
    return ..... 
}

export default withSanctum(SubAppComponents) 

What do you think?

config properties are undefined

I am initializing the Sanctum with the following code:

import { Sanctum } from "react-sanctum";
const sanctumConfig = {
  api_url: "https://example.com",
  csrf_cookie_route: "sanctum/csrf-cookie",
  singin_route: "login",
  signout_route: "logout",
  user_object_route: "api/user"
}

ReactDOM.render((
  <Sanctum config={sanctumConfig}>
    <Router >
        <Switch>
          <Route exact={true} path="/queues" component={Queues} />
          <Route exact={true} path="/queues/:id" component={Queue} />
          <Route exact={true} path="/login" component={Login} />
        </Switch>
      </Router>
  </Sanctum>),
  document.getElementById('root')
);

In my Login-Component, I am trying to use the signIn-Method:

import { SanctumContext, withSanctum } from 'react-sanctum';

export default function LoginPage()
{
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");
    const [loginSuccess, setLoginSuccess] = useState(false);
    
    const context = useContext(SanctumContext);
    console.log(context.signIn("test", "Test", false));

   return (...)
}

My Console shows the following error:

Access to XMLHttpRequest at 'https://example.com/undefined' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

As you can see, I set my sign_in route to "login", but he isn't calling the correct url (example.com/login). Instead, he is calling example.com/undefined.

I also tried it with the withSanctum-way, but there is the same behaviour...

Does this work with Sanctum tokens?

I've got a react app that will be running on a different domain from my api. My understanding is that I can't use stateful Sanctum logins so Laravel Breeze is a no-go. From the docs it looks like I need to use Sanctum's token login. Does this package support that? Do you have any advice?

Redirect if not authenticated

Hi, I am enjoying this package, works perfect so far. Thank you.

Just come here to ask you if you have a best way to redirect to another page if the user is not authenticated. For example lets say "dashboard" it should redirect if not logged in.

This is my page

import React, { Component } from 'react'

import { Default } from '@/components/templates/Default'

class Dashboard extends Component {

    render() {
        return (
            <>
                <Default description="Dashboard Description" title="User's Dashboard">
                    <div className="user-area-all-style sign-up-area pt-100 bg-black">
                        Dashboard Content
                    </div>
                </Default>
            </>
        )
    }
}

export default Dashboard

[Help] can you use this package with react native?

I am looking to sign in and authenticate my users with a Laravel Sanctum token and would like to get user data into my react native app once the user is logged in. is it possible to achieve this in react native, with your package.

Accessing to csrf token

Hello,
For custom post request using this package, how can I provide CSRF token to my API ?
Because actually, my api returns error 419 unknown status due to csrf token ?

React peer requirement not recognized

When I run npm update, I get these warnings:

npm WARN [email protected] requires a peer of react@^16.13.0 but none is installed. You must install peer dependencies yourself.
npm WARN [email protected] requires a peer of react-dom@^16.13.0 but none is installed. You must install peer dependencies yourself.

This is my package.json:

"dependencies": {
"axios": "^0.21.0",
"next": "10.0.0",
"next-images": "^1.6.2",
"react": "17.0.1",
"react-dom": "17.0.1",
"react-icons": "^3.11.0",
"react-sanctum": "^1.0.0"
},
"devDependencies": {
"@fullhuman/postcss-purgecss": "^3.0.0",
"@types/node": "^14.14.6",
"@types/react": "^16.9.55",
"@typescript-eslint/eslint-plugin": "^4.6.1",
"@typescript-eslint/parser": "^4.6.1",
"autoprefixer": "^10.0.1",
"babel-eslint": "^10.1.0",
"eslint": "^7.12.1",
"eslint-config-airbnb": "^18.2.0",
"eslint-config-airbnb-typescript": "^12.0.0",
"eslint-config-react-app": "^6.0.0",
"eslint-plugin-flowtype": "^5.2.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-react": "^7.21.5",
"eslint-plugin-react-hooks": "^4.2.0",
"postcss-flexbugs-fixes": "^5.0.0",
"postcss-preset-env": "^6.7.0",
"sass": "^1.28.0",
"tailwindcss": "^1.9.6",
"typescript": "^4.0.5"
},

This should fulfill it, right?

"react": "17.0.1",
"react-dom": "17.0.1",

Redirect if not authenticated?

Hello there!
I am trying to implement this into a new project. Is it possible to redirect the user in case he is not authenticated?

This is what I tried so far but obviously, React is not waiting for react-sanctum to call the API and set the state of the user object.

Is there any way I can redirect the user after the authenticated prop is set as it should be?

class App extends Component {
  render() {
    if (this.props.authenticated) {
      return <MyComponent />;
    } else {
      window.location.href = 'http://localhost:8000/login'
    }
  }
}

Old axios version in NPM release

Hi!

Would be nice to make a release to npm with newer axios. Your library use axios "^0.27.2".
Sadly can't use custom axiosInstance with the new axios.

Thank you!

TypeError: Cannot read property 'useContext' of null

Trying to use react-sanctum in middleware and I'm getting the following error: TypeError: Cannot read property 'useContext' of null

const { authenticated } = useSanctum();

I have imported useSanctum().

Any ideas?

Version 2.1.3 still has the Children type error

Hi there,
Awesome package!

But I ran into an issue with react 18.2
While using version 2.1.3 i still get the following error:
TS2322: Type '{ children: Element; config: { apiUrl: string; csrfCookieRoute: string; signInRoute: string; signOutRoute: string; userObjectRoute: string; }; checkOnInit: true; }' is not assignable to type 'IntrinsicAttributes & Props'.   Property 'children' does not exist on type 'IntrinsicAttributes & Props'.

If I take a look at the Sanctum.d.ts it still looks like this:

import { AxiosInstance } from "axios";
interface Props {
    config: {
        apiUrl: string;
        csrfCookieRoute: string;
        signInRoute: string;
        signOutRoute: string;
        userObjectRoute: string;
        twoFactorChallengeRoute?: string;
        axiosInstance?: AxiosInstance;
    };
    checkOnInit?: boolean;
}
declare const Sanctum: React.FC<Props>;
export default Sanctum;

Whilst on your Github there should indeed be a Children element in the props. I'm on version 2.1.3 for sure as my yarn.lock states:

react-sanctum@^2.1.3:
  version "2.1.3"
  resolved "https://registry.yarnpkg.com/react-sanctum/-/react-sanctum-2.1.3.tgz#acf8f97bb482e1f38130baddf42e92a4b7fbedee"
  integrity sha512-J4MPrlSkgY012H1CPQBdQrUFkRkZWMPkSI+HARzw0eigMP1wCAo7hcZNCx153dzIq2Pi9LcbtCeQ0QsYIouYmw==
  dependencies:
    axios "^0.26.0" 

Any way to fix that?

SanctumContext: Cannot invoke an object which is possibly 'undefined'.ts

What im trying to do:

const handleLogin = async (e: any) => {
  e.preventDefault();
  const {signIn} = React.useContext(SanctumContext);
  const sign = await signIn('[email protected]', '12345');
}

Error:

Cannot invoke an object which is possibly 'undefined'.ts(2722) (property) signIn?: ((email: string, password: string, remember?: boolean | undefined) => Promise<{}>) | undefined

What does the login page need to return?

In the docs, it says:

// {email: string, password: string, remember: true | null} get POSTed to here
signin_route: "api/login",

Does this need to return a plain text sanctum token or something else? Right now my api/tokens request returns a key and it fails via 401 on the /users request.

Unauthenticated result

I'm absolutely sure that I'm missing something, but I just can't figure it out. Here's my login:

  const LoginButton = () => {
    const { authenticated, user, signIn } = useSanctum();

    const handleLogin = () => {
      signIn(userEmail.current.value, userPassword.current.value, rememberMe)
        .then(() => (
          <MDAlert color="error" dismissible>
            {alertContent("Login Success", "You're logged in!")}
          </MDAlert>
        ))
        .catch((result) => (
          <MDAlert color="error" dismissible>
            {alertContent("Login Error", result.message)}
          </MDAlert>
        ));
    };

    if (authenticated === true) {
      return <h1>Welcome, {user.name}</h1>;
    }
    return <MDButton onClick={handleLogin}>Sign in</MDButton>;
  };

Here's my backend login method:

    public function login(Request $request)
    {
        $fields = $request->validate([
            'email'    => 'required|string',
            'password' => 'required|string',
        ]);

        // Check email
        $user = User::where('email', $fields['email'])->first();

        // Check password
        if (!$user || !Hash::check($fields['password'], $user->password)) {
            return response([
                'message' => 'Unable to find an account with that username/password combination.',
            ], 401);
        }

        $token = $user->createToken('budget-api')->plainTextToken;

        $response = [
            'user'  => $user,
            'token' => $token,
        ];

        return response($response, 201);
    }

I'm noticing 2 calls to my api/login route: a POST and an OPTIONS. However, when I do my api/user call, I get a 401 back. Any help would be appreciated.

Unnecessary Accept headers being added text/plain, */*

Hi there,

Really like this library, it's been a great help getting up and running with React and Sanctum!

I seem to be having a slight issue when using the signIn function where I am getting "text/plain, /" appended to "application/json" in the Accept header, which in turn is trying to do the normal redirect on login stuff from in Laravel instead of detecting it as an API request. I'm struggling to figure out where this is coming from but am struggling a bit. Can anyone suggest anything where I am going wrong?

I'm using Windows 10 and Brave V1.16.76, but seems to happen on Firefox too.

Load user data before page load

Is there any way for the user data to be fetched before the page loads? I've got the users name on the homepage and its briefly undefined until the page finishes loading.

Using NextJs and checkOnInit runs authentication twice

I have a fresh application using NextJS, and my implementation looks like this:

function MyApp({ Component, pageProps }) {
  return (
    <Sanctum config={sanctumConfig} checkOnInit>
      <Component {...pageProps} />
    </Sanctum>
  )
}

And in my network tab, on every page load loads my user_object_route twice, once successfully, and once with a 401 error. The code above is my only react-sanctum code added, there's nothing else going on. Did I do something wrong?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.