I am learning about react and django. I have installed django-rest-auth to handle account creations and authentication for users. I also wanted to learn about react and I have install axios to make http request to my django rest api. I want to have a "splash" page where users would first access the site. If the user is already logged in they'll see their profile and other content. If the user isn't logged in they should be presented a login page.
Here's my App.js code I have so far.
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import logo from './logo.svg';
import './App.css';
function LoginPage(props) {
console.log('LoginPage props are:');
console.log({ props });
return (<div className="LoginPage">props are: {props}</div>)
}
function SplashPage(props) {
const [currentUser, setCurrentUser] = useState(null);
console.log('SplashPage props are:');
console.log({ props });
const userUrl = 'http://localhost:8000/rest-auth/user/';
console.log('userUrl is:' + userUrl);
axios.get(userUrl)
.then(res => { setCurrentUser(res.data); })
.catch((error) => {
console.log(error.response);
return (<div><LoginPage /></div>);
})
return (<div className="SplashPage">[{userUrl}] [{currentUser}] </div>);
}
function App() {
return (
<div>
<SplashPage />
</div>
);
}
export default App;
Heres my index.js file:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers:
serviceWorker.unregister();
When I go to http://localhost:3000 I get this result:
In the developer console looks like
I had hoped to see the content of my LoginPage function.
[UPDATED ANSWER]
You are returning <div className="SplashPage">[{userUrl}] [{currentUser}] </div> before <div><LoginPage /></div> because it is outside the axios .then() chain ( ie it called directly after the axios.get() and before any code in the .then() or the .catch() blocks )
Should work:
initialize a current user with a loaderState to avoid content flicker
Update state within the axios .then() or .catch()
Use state to determine what to return from function outside of the promises
-
function SplashPage(props) {
const [currentUser={notLoaded:true}, setCurrentUser] = useState(null);
const userUrl = 'http://localhost:8000/rest-auth/user/';
axios.get(userUrl).then(res => {
setCurrentUser(res.data);
}).catch((error) => {
console.log(error)
setCurrentUser(null)
})
//user no authorized
if(!currentUser)
return <LoginPage />
//user authorization unknown
if(currentUser.notLoaded)
return <div/>
//we have a user!
return <div className="SplashPage">{userUrl} {currentUser}</div>
}
[ORIGINAL ANSWER]
EDIT: sorry I misunderstood your question but will leave my original answer here in case someone comes looking for a related issue.
You are getting a 403 error with the message:
Authentication credentials not provided
You need to add some sort of authorization to your request (consult your django-rest-auth configuration/documentation for how it expects authorization from incoming requests).
You can either set this up for every api call manually or set this up via axios.interceptors.request.use() which you will need to import and call somewhere in your application (such as in your app.js or index.js)
The following example:
uses axios.interceptors
adds an authorization token to the Authorization header
utilizes the standard 'bearer TOKEN'
uses firebase auth to demonstrate retrieving token via async
(your actual implementation will depend on how your api is set up and your authorization flow)
addAuthHeader.js:
import axios from 'axios';
import * as firebase from 'firebase/app';
const apiUrl = 'http://localhost:8000/' // '/' if using the preferred http-proxy-middleware
export default addAuthHeader = () =>
//if firebase auth callback should be asyncasync
axios.interceptors.request.use(async (config) => {
if(config.url.startsWith(apiUrl)){
const token = await firebase.auth().currentUser.getIdToken(true)
config.headers.Authorization = `bearer ${token}`;
return config;
}
});
App.js:
addAuthHeader()
Related
I configured and initialized AWS Amplify for my ReactNative/Expo app and added a REST Api. Im new to AWS in general, but im assuming that once I add the API, my project is populated with amplify/backend folders and files and is ready for consumption.
So i tried to create a simple post request to create an item in my DynamoDB table with
import { Amplify, API } from "aws-amplify";
import awsconfig from "./src/aws-exports";
Amplify.configure(awsconfig);
const enterData = async () => {
API.post("API", "/", {
body: {
dateID: "testing",
},
headers: {
Authorization: `Bearer ${(await Auth.currentSession())
.getIdToken()
.getJwtToken()}`
}
})
.then((result) => {
// console.log(JSON.parse(result));
})
.catch((err) => {
console.log(err);
});
};
const signIn = async () => {
Auth.signIn('test#test.com', 'testpassword')
.then((data) => {
console.log(data)
enterData() //enterData is attempted after signin is confirmed.
})
.catch((err) => {
console.log(err)
})
}
signIn()
I did not touch anything else in my project folder besides including the above in my App.tsx because im unsure if i need to and where. I got a 403 error code and it "points" to the axios package but im not sure if issue is related to aws integration.
I configured the REST Api with restricted access where Authenticated users are allowed to CRUD, and guests are allowed to Read. How could I even check if I am considered an "Authorized User" .
Yes, AWS Amplify API category uses Axios under the hood so axios is related to your problem.
Probably you get 403 because you didn't authorized, for Rest API's you need to set authorization headers,
I don't know how is your config but you can take help from this page. Please review the "Define Authorization Rules" section under the API(REST) section.
https://docs.amplify.aws/lib/restapi/authz/q/platform/js/#customizing-http-request-headers
To check authorization methods, you can use "Auth" class like that also you can see auth class usage in the above link.
import { Amplify, API, Auth } from "aws-amplify";
https://aws-amplify.github.io/amplify-js/api/classes/authclass.html
I'm trying to use React to consume a Django Rest Framework-based API, but am having some major problems with what should be super simple.
Given this simple API method:
from rest_framework.decorators import api_view, detail_route, list_route, permission_classes
#api_view(['GET'])
#permission_classes((AllowAny,))
def dummy(request, per_page=40):
import json
print("Returning the dummy")
return Response({"Yeah":"Booo!"})
And this function in React using Axios to consume it:
import React, { Component } from "react";
import PropTypes from "prop-types";
import axios from 'axios';
class Dashboard extends Component{
constructor(props){
super(props);
this.state = {
username: props.username,
};
axios.defaults.xsrfCookieName = 'csrftoken';
axios.defaults.xsrfHeaderName = 'X-CSRFToken';
}
componentDidMount(){
axios.get('/api/dummy/').then((response) => console.log(response));
}
render(){
return(
<div id="dashboardWrapper"></div>
)
}
}
export default Dashboard
Using Python's requests library and curl, the method returns a JSON object. In the browser, loading this page will run the GET function, and also load the JSON object in the Network tab. This leads me to believe that the problem isn't on Django's end. Additionally, I tried this with a third-party API (https://dog.ceo/api/breeds/image/random) and get the same problem.
When I look in the console, the Axios GET function will not capture the response sent up from Django. It gets a status code 200, but nothing else. No response.data at all. With console.log(response) all I see is , for both errors (have tested on non-existant endpoints) and valid endpoints.
Using Fetch gets the same result.
Strangely enough, Axios POST works but also doesn't capture any response afterwards.
What could the cause and solution be? Thanks for the help!
This turned out to be a Firefox issue, not a React or DRF issue. Related to: Object 'unavailable' in Firefox console
By using the first example in the first answer, I was able to get it to properly display in the browser console:
axios.get('/api/dummy/')
.then((response) => console.log("Data",JSON.stringify(response, null, 4)));
I am currently using the vue-apollo package for Apollo client with VueJs stack with django and graphene-python for my GraphQl API.
I have a simple setup with vue-apollo below:
import Vue from 'vue'
import { ApolloClient } from 'apollo-client'
import { HttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import VueApollo from 'vue-apollo'
import Cookies from 'js-cookie'
const httpLink = new HttpLink({
credentials: 'same-origin',
uri: 'http://localhost:8000/api/',
})
// Create the apollo client
const apolloClient = new ApolloClient({
link: httpLink,
cache: new InMemoryCache(),
connectToDevTools: true,
})
export const apolloProvider = new VueApollo({
defaultClient: apolloClient,
})
// Install the vue plugin
Vue.use(VueApollo)
I also have CORS setup on my Django settings.py with the django-cors-headers package. All queries and mutations resolve fine when I use graphiQL or the Insomnia API client for chrome, but trying the mutation below from my vue app:
'''
import gql from "graphql-tag";
import CREATE_USER from "#/graphql/NewUser.gql";
export default {
data() {
return {
test: ""
};
},
methods: {
authenticateUser() {
this.$apollo.mutate({
mutation: CREATE_USER,
variables: {
email: "test#example.com",
password: "pa$$word",
username: "testuser"
}
}).then(data => {
console.log(result)
})
}
}
};
NewUser.gql
mutation createUser($email: String!, $password: String!, $username: String!) {
createUser (username: $name, password: $password, email: $email)
user {
id
username
email
password
}
}
returns with the error response below:
POST http://localhost:8000/api/ 400 (Bad Request)
ApolloError.js?d4ec:37 Uncaught (in promise) Error: Network error: Response not successful: Received status code 400
Regular queries in my vue app, however, work fine resolving the right response, except mutations, so this has me really baffled
400 errors generally mean there's something off with the query itself. In this instance, you've defined (and you're passing in) a variable called $username -- however, your query references it as $name on line 2.
In addition to graphiQL, I would like to add that apollo-link-error package would also had been of great help.
By importing its error handler { onError }, you can obtain great detail through the console about errors produced at network and application(graphql) level :
import { onError } from 'apollo-link-error';
import { ApolloLink } from 'apollo-link';
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
console.log('graphQLErrors', graphQLErrors);
}
if (networkError) {
console.log('networkError', networkError);
}
});
const httpLink = ...
const link = ApolloLink.from([errorLink, httpLink]);
const client = new ApolloClient({
...,
link,
...
});
By adding this configuration where you instantiate your Apollo Client, you would have obtained an error similar to this one:
GraphQLError{message: "Syntax Error: Expected {, found Name "createUser""}
Further information can be found in Apollo Doc - Error handling: https://www.apollographql.com/docs/react/features/error-handling.
Hope it helps in the future.
For me, it was the fact that I was using a field not defined in the GraphQL schema. Always be careful!
For sure the mutation is not formatted correctly if that is exactly what you are sending. You need an opening bracket in the mutation
mutation createUser($email: String!, $password: String!, $username: String!) {
createUser (username: $name, password: $password, email: $email) {
user {
id
username
email
password
}
}
}
With any of these queries when i run into bugs i paste it into either graphiql or graphql playground to identify what the formatting errors is in order to isolate what is wrong.
For people using laravel for backend, this helped me solve the problem
In the laravel project find file config/cors.php and change line 'paths' => ['api/*', 'sanctum/csrf-cookie'], to 'paths' => ['api/*', 'graphql', 'sanctum/csrf-cookie'],
Also in your vue app ensure that you're not using the no-cors mode in apollo config
Regards
I've taken a ReactJS component (rendering the latest gist URL for a given user) from the React docs, and was wondering what is the best way to unit test such a component :
The goals are
Test in isolation (using mocked http calls)
Use our existing test setup (mocha)
Keep things simple
Verify that eventually, when the http call in the component success, the state change triggered a re-render, and an anchor element is rendered the proper url in it.
Here's the component I want to test:
import React from 'react'
import $ from 'jquery'
export default React.createClass({
getInitialState: function() {
return {
username: '',
lastGistUrl: ''
};
},
componentDidMount: function() {
fetch(this.props.source).then(function(response) {
return response.json()
}).then(function(json) {
this.setState({
username: json[0].owner.login,
lastGistUrl: json[0].html_url
});
}.bind(this)).catch(function(ex) {
console.log('parsing failed', ex)
})
},
render: function() {
return (
<div>
{this.state.username}'s last gist is
<a href={ this.state.lastGistUrl}>here</a>.
</div>
);
}
});
And here is my first attempt at testing it:
import TestUtils from 'react-addons-test-utils'
import React from 'react'
import { expect } from 'chai'
import { findDOMNode } from 'react-dom'
import UserGist from '../assets/js/components/UserGistWithFetch'
import nock from 'nock'
describe('UserGistWithFetch', () => {
it('Displays the correct url', (done) => {
nock.disableNetConnect();
nock('https://api.github.com')
.get('/users/octocat/gists')
.reply(200, [{owner:"octocat",html_url:"https://gist.github.com/6cad326836d38bd3a7ae"}])
const gist = TestUtils.renderIntoDocument(<UserGist source="https://api.github.com/users/octocat/gists"/>)
let a = TestUtils.scryRenderedDOMComponentsWithTag(gist, 'a')[0]
expect(a.getAttribute('href')).to.be.equal("https://gist.github.com/6cad326836d38bd3a7ae")
done()
})
})
This test obviously fails, as the component is initially rendered before the mock callback is executed, not rendering the anchor correctly.
The test fails before the mocked http call returns, and the component doesn't get a chance to re-render.
From what I understand, Mocha provides ways to do async testing (using the done() function), but I can't find a hook in my test to put this.
What tools / frameworks would I need to accomplish that ?
I have an Angular 2 service that has a logout function. When the function is called from the app component it cause a full page refresh. When using angular 1 projects I haven't experienced this behavior. If I call my logout endpoint with postman the session cookie is deleted. It is not deleted if I use my angular 2 authentication service.
Service
import {Injectable} from 'angular2/core';
import {User} from './user';
import {Headers, RequestOptions, Http, Response} from 'angular2/http';
import {Observable} from 'rxjs/Observable';
import {Cookie} from '../extensions/cookies';
#Injectable()
export class AuthenticationService {
constructor (private http: Http) {}
private _prepTestHost = 'http://localhost:8000/';
private _prepTestLoginUrl = this._prepTestHost + 'login/';
private _prepTestLogoutUrl = this._prepTestHost + 'logout/';
private _authenticated: boolean;
getUser() {}
isAuthenticated() {
return this._authenticated;
}
setAuthenticated() {
this._authenticated = true;
}
loginUser(username, password) : Observable<User> {
let body = JSON.stringify({username, password});
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.post(this._prepTestLoginUrl, body, options)
.map(res => <User> res.json(), this.setAuthenticated())
.catch(this.handleError)
}
logoutUser() : Observable<void> {
let body = JSON.stringify({});
let csrfcookie = Cookie.getCookie('csrftoken');
let headers = new Headers({
'X-CSRFToken': csrfcookie,
'Content-Type': 'application/json'
});
let options = new RequestOptions({ headers: headers});
return this.http.post(this._prepTestLogoutUrl, body, options)
.map(res => <void> res.json())
.catch(this.handleError);
}
private handleError (error: Response) {
// in a real world app, we may send the server to some remote logging infrastructure
// instead of just logging it to the console
console.error(error);
return Observable.throw(error.json().error || 'Server error');
}
}
App Component
import {Component} from 'angular2/core';
import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';
import {WelcomeCenterComponent} from './welcome-center/welcome-center.component';
import {AuthenticationService} from './authentication/authentication.service';
import {LoginModalComponent} from './authentication/login-modal.component';
import {BrowserXhr, HTTP_PROVIDERS} from "angular2/http";
import {CORSBrowserXHR} from './extensions/corsbrowserxhr';
import {provide} from "angular2/core";
#Component({
selector: 'my-app',
template: `
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="#" [routerLink]="['WelcomeCenter']">Brand</a>
</div>
<ul class="nav navbar-nav navbar-right">
<li *ngIf="!authenticated()">
Login
</li>
<li *ngIf="authenticated()">
Logout
</li>
</ul>
</div>
</nav>
<router-outlet></router-outlet>
<login-modal></login-modal>
`,
directives: [ROUTER_DIRECTIVES, LoginModalComponent],
providers: [HTTP_PROVIDERS,
provide(BrowserXhr, {useClass: CORSBrowserXHR}),
AuthenticationService]
})
#RouteConfig([
{
path: '/welcome-center/...',
name: 'WelcomeCenter',
component: WelcomeCenterComponent,
useAsDefault: true
}
])
export class AppComponent {
constructor(private _authenticationService: AuthenticationService) {}
authenticated() {
return this._authenticationService.isAuthenticated();
}
logout() {
console.log("Logout button pressed");
this._authenticationService.logoutUser().subscribe();
}
}
Setting withCredentials attribute:
import {BrowserXhr, HTTP_PROVIDERS} from "angular2/http";
import {Injectable, provide} from "angular2/core";
#Injectable()
export class CORSBrowserXHR extends BrowserXhr{
build(): any{
var xhr:any = super.build();
xhr.withCredentials = true;
return xhr;
}
}
I think that the page reload is due to the fact that you don't prevent event propagation when blocking on the layout button (you an 'a' HTML element with an 'href' attribute). You could use 'return false' at the end of your logout function or '$event.stopPropagation()'.
See the question for more details:
Stop event propagation in Angular 2
Regarding the cookie problem, I these that you use cross domain requests (CORS). I think that you should try to set to true the 'withCredentials' attribute on the underlying XHR object. See this question for more details:
Set-cookie in response not set for Angular2 post request
You could do something kind of hacky but I can't think of another way.
Cookie.setCookie(nameOfCookie, "", -1);
This would effectively delete the cookie on logout. I'd love to know if there was a better way though!
I also am not sure why you are getting any kind of page reload at all, I have yet to experience that on anything I've done yet, hopefully someone else will know.