I have an API hub that I've built in Django and a frontend end application I've built in NextJS. I'm currently working on authenticating to the Django API in Nextjs and I'm curious about best practices.
Currently, the NextJS app posts the users username/password to an endpoint. This endpoint either returns the users token or the error illustrating the issue.
React
const login = async () => {
let token = await axios.post('/api/accounts/', {
email: email,
password: password
}).then(r => r.data.token).catch(function (error) { console.log(error) })
if (token) {
router.push({
pathname: '/home/',
query: { token: token },
})
}
}
nexjs server api/accounts
export default async (req, res) => {
if (req.method === 'POST') {
try {
// retrieve payment intent data
const {data} = await axios.post('https://website/api/api-token-auth/', req.body)
res.status(200).send(data)
} catch (err) {
res.status(500).json({ statusCode: 500, message: err.message })
}
} else {
res.setHeader('Allow', 'POST')
res.status(405).end('Method Not Allowed')
}
}
Django API
#csrf_exempt
#api_view(["POST"])
#permission_classes((AllowAny,))
def obtain_auth_token(request):
email = request.data.get("email")
password = request.data.get("password")
if email is None or password is None:
return Response({'error': 'Please provide both email and password'},
status=HTTP_400_BAD_REQUEST)
user = authenticate(email=email, password=password)
if not user:
return Response({'error': 'Invalid Credentials'},
status=HTTP_404_NOT_FOUND)
token, _ = Token.objects.get_or_create(user=user)
return Response({'token': token.key},
status=HTTP_200_OK)
Once I receive the token I push the user to the homepage.
My questions are:
Is how I'm authenticating users a good way to do this? Am I overlooking something? This is the first time I've attempted to authenticate to something I've built so I want to get this right.
How should I store this token? What is "best practice" when it comes to authentication creds? I've thought about passing the token around to every component that needs it. I've also peeked at using LocalStorage but again am unsure what most people do in these situations.
Any help you all can provide would be much appreciated!
Thanks in advance!
Related
I come to you after hours of research.
I have created my facebook account with the application I am working on and also my firebase account that I have linked through the OAuth redirection URI to the configuration of your Facebook application.
But I always get the same mistake. Do you have any leads? Knowing that my APP_ID is the same in the code, on facebook developers and Firebase. And that I redirected the URI of firebase in facebook developpers.
Here is my code :
async function loginWithFacebook(){
await Facebook.initializeAsync({
appId : '1027709424451081'});
const {type,token} =
await Facebook.logInWithReadPermissionsAsync({
permissions:['public_profile'],
});
if (type === 'success') {
const credential = firebase.auth.FacebookAuthProvider.credential(token);
Firebase.auth().signInWithCredential(credential)
.then(user => { // All the details about user are in here returned from firebase
console.log('Logged in successfully', user)
})
.catch((error) => {
console.log('Error occurred ', error)
});
}
}
Thanks in advance for all suggestions.
When using the function "signInWithCredentials" you need to pass the auth also which you will get by
const auth = getAuth()
and then after getting the credentials
signInWithCredential(auth, credential)
I'm using express as my backend and I'm using the Firebase Admin SDK to send back the token to the client.
At the moment the token is expired after 1 Hour. I read on the firebase that there isn't any way to change the expiration property because the way it works - the user will get a refreshed token every hour. Is that correct? If so, how I supposed to implement it?
Here is my login route:
exports.login = async (req, res) => {
const user = {
email: req.body.email,
password: req.body.password,
}
// Validate Data
const { valid, errors } = validateLogin(user)
if (!valid) return res.status(400).json(errors)
try {
const data = await firebase
.auth()
.signInWithEmailAndPassword(user.email, user.password)
const token = await data.user.getIdToken()
const cookieOptions = {
httpOnly: true,
secure: false,
}
res.cookie('jwt', token, cookieOptions)
return res.status(201).json({ token })
} catch (err) {
errors.general = 'Wrong credentials, please try again'
return res.status(403).json(errors)
}
}
There is nothing to do on the backend to implement token refreshing. The client app will do that automatically using the Firebase Auth SDK.
Anyway, backend apps aren't supposed to sign in the user - that won't work out the way you expect. When using Firebase Auth, client apps are supposed to sign in using the client SDK. The client app signs in, gets a token, then passes that token to the backend to be verified using the Firebase Admin SDK, then acted on.
I am using mern stack to make an application. For authentication i am using toke auth with passport authentication and for security reasons i am sending token in cookie. I have a login call which returns a cookie with response. The snippet is below:
res.cookie("cookie_token", token, { maxAge: 84600 });
res.send({
status: "success"
});
I can see the cookie in postman and even in browser in network(xhr request).
I am using axios for making call to the login api in react js.
axios.get(myapiurl, {
headers: {
email: fields.email,
password: fields.password,
"access-control-allow-origin": "*"
}
})
.then(res => {
console.log(res);
}).catch((err) => {
console.log(err);
});
Though i can't find a snippet to access the cookie in react js. How can i parse it? I can't see cookie in response of axios though? How can i access it.
Please try this. Snippet from mdn.
function getCookie(sKey) {
if (!sKey) { return null; }
return document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + sKey.replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1") || null;
}
export function getLoginInfo() {
const cookieToken = getCookie('cookie_token')
console.log(cookieToken)
}
I have successfully created an API that uses passport-google-oauth to return a JWT. Currently when I go through the process using my API routes it returns a json object with a JWT Bearer token.
I am attempting to use Reactjs on the front end however am running into a couple issues.
In my signin button component I am just trying to retrieve the result with the bearer token to pass it into a reducer
When using Axios -> I am running into a CORS issue when using exios and cant return a result, when adding CORS into my build and a proxy to my react project I recieve the following error No 'Access-Control-Allow-Origin' header is present on the requested resource.
When I use a anchor tag with href link the authentication successfully works however it redirects to the /api/auth/google/callback link itself instead of allowing me to catch the bearer token and then run it through my reducers to save it into local storage and update my state.
Am I missing a step? Ive looked for a few hours at various resources online and cant seem to find the solution im looking for
React
(for simplicity at the moment I am just trying to catch the response, which should be returned bearer token, however I am unable to do this)
googleAuth = (e) => {
e.preventDefault()
axios.get('/api/auth/google')
.then(res => console.log(res))
.catch(err => console.log(err))
}
render() {
return (
<button onClick={this.googleAuth}>Signin With Google</button>
)
}
API
Routes
router.get('/google', passport.authenticate('google', {
session: false,
scope: ['profile', 'email']
}))
router.get('/google/callback', passport.authenticate('google', { session: false }), generateUserToken)
Strategy
passport.use(new passportGoogle.OAuth2Strategy(googleConfig, async (request, accessToken, refreshToken, profile, done) => {
// Check for existing user
const existingUser = await User.findOne({
providers: {
$elemMatch: {
provider: 'Google',
providerId: profile.id
}
}
})
// If user exists return done
if (existingUser) return done(null, existingUser)
// If user does not exist create a new user
const newUser = await new User({
name: profile.displayName,
providers: [
{
provider: 'Google',
providerId: profile.id
}
]
}).save()
// Create profile with new user information
const newProfile = await new Profile({
userId: newUser.id
}).save()
return done(null, newUser)
}))
I've looked a bit at your code and haven't seen any serializing/deserializing going on. Usually you'd have to go through this process to be able to connect with whatever authentication strategy you are using.
Here is a snippet of code that you could use in the file you keep your strategies:
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findById(id).then(user => {
done(null, user);
});
});
Maybe you could look it up in more detail in the documentation. Here is a link http://www.passportjs.org/docs/ go to the sessions part. Plus, make sure to look at how the app.use is put together with the .session() func.
After a bit of research, JWT is commonly used for login authentication because of its compact nature and easiness to parse. I have settled on using JWT. However, my question is on how to embed this in my redux paradigm. Assuming we have a sign up form, when a user fills in his or her credentials and clicks a submit button, this will invoke an action to create an action to create a JWT. Now, this action goes to the back-end of my application and the back-end of my application calls the JWT API? So this action is an asynchronous/rpc call? Also, how does routing happen exactly? I have used react-router before, but using a boilerplate. I am building this web app from scratch and so I am a bit confused on where to deal with the routing and where do I pass this token exactly that I obtain from the server the first time? Is the token used every time a user does a request? How does the client know about this token every time it does the request so that it would keep a user authenticated?
When a user submits his credentials (email/password) your backend authenticates that for the first time and only this time does the backend use these credentials. On authentication your backend will create a JWT with some of the user information, usually just the user ID. There are plenty of JWT Libraries and even jwt-decode for javascript to do this. The backend will respond with this JWT where the front-end will save it (ie, localStorage.setItem('authToken', jwt)) for every subsequent request.
The user will send a request with the JWT in the request header under the Authorization key. Something like:
function buildHeaders() {
const token = localStorage.getItem('authToken')
return {
"Accept": "application/json",
"Content-Type": "application/json"
"Authorization": `${token}`
}
}
Your backend will now decode and authenticate the JWT. If it's a valid JWT the request continues, if not it's rejected.
Now with React-Router you can protect authenticated routes with the onEnter function. The function you provide does any necessary checks (check localStorage for JWT and if a current user). Typically I've done this:
const _ensureAuthenticated = (nextState, replace) => {
const { dispatch } = store
const { session } = store.getState()
const { currentUser } = session
const token = localStorage.getItem("phoenixAuthToken")
if (!currentUser && token) { // if no user but token exist, still verify
dispatch(Actions.currentUser())
} else if (!token) { // if no token at all redirect to sign-in
replace({
pathname: "/sign-in",
state: { nextPathname: nextState.location.pathname}
})
}
}
You can use this function in any route like so:
<Route path="/secret-path" onEnter={_ensureAuthenticated} />
Check out jwt.io for more information on JWT's and the react-router auth-flow example for more information on authentication with react-router.
I personally use Redux saga for async API calls, and I'll show You the flow I've been using for JWT authorization:
Dispatch LOG_IN action with username and password
In your saga You dispatch LOGGING_IN_PROGRESS action to show e.x. spinner
Make API call
Retrieved token save e.x. in localstorage
Dispatch LOG_IN_SUCCESS or LOG_IN_FAILED to inform application what response did You get
Now, I always used a separate function to handle all my requests, which looks like this:
import request from 'axios';
import {get} from './persist'; // function to get something from localstorage
export const GET = 'GET';
export const POST = 'POST';
export const PUT = 'PUT';
export const DELETE = 'DELETE';
const service = (requestType, url, data = {}, config = {}) => {
request.defaults.headers.common.Authorization = get('token') ? `Token ${get('token')}` : '';
switch (requestType) {
case GET: {
return request.get(url, data, config);
}
case POST: {
return request.post(url, data, config);
}
case PUT: {
return request.put(url, data, config);
}
case DELETE: {
return request.delete(url, data, config);
}
default: {
throw new TypeError('No valid request type provided');
}
}
};
export default service;
Thanks to this service, I can easily set request data for every API call from my app (can be setting locale also).
The most interesting part of it should be this line:
request.defaults.headers.common.Authorization = get('token') ? `Token ${get('token')}` : '';`
It sets JWT token on every request or leave the field blank.
If the Token is outdated or is invalid, Your backend API should return a response with 401 status code on any API call. Then, in the saga catch block, you can handle this error any way You want.
I recently had to implement registration and login with React & Redux as well.
Below are a few of the main snippets that implement the login functionality and setting of the http auth header.
This is my login async action creator function:
function login(username, password) {
return dispatch => {
dispatch(request({ username }));
userService.login(username, password)
.then(
user => {
dispatch(success(user));
history.push('/');
},
error => {
dispatch(failure(error));
dispatch(alertActions.error(error));
}
);
};
function request(user) { return { type: userConstants.LOGIN_REQUEST, user } }
function success(user) { return { type: userConstants.LOGIN_SUCCESS, user } }
function failure(error) { return { type: userConstants.LOGIN_FAILURE, error } }
}
This is the login function of the user service that handles the api call:
function login(username, password) {
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
};
return fetch('/users/authenticate', requestOptions)
.then(response => {
if (!response.ok) {
return Promise.reject(response.statusText);
}
return response.json();
})
.then(user => {
// login successful if there's a jwt token in the response
if (user && user.token) {
// store user details and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('user', JSON.stringify(user));
}
return user;
});
}
And this is a helper function used to set the Authorization header for http requests:
export function authHeader() {
// return authorization header with jwt token
let user = JSON.parse(localStorage.getItem('user'));
if (user && user.token) {
return { 'Authorization': 'Bearer ' + user.token };
} else {
return {};
}
}
For the full example and working demo you can go to this blog post