I completed manipulating authentication with token by referring this article, and then I’m trying to create a crud function such creating post, displaying posts, etc… . However, I have an error when I fetched the url which displays posts(IE, fetching url I defined as “index” method on views.py of app for auth manipulation), I have 401 error even though I can access by using url of the backend without any error even on terminal.
I found some config codes which are related to authentication and permission for manipulation of authentication with token on settings.py causes this error, since when I delete these codes, the crud function works. But obviously authentication function no longer works (index method on views.py retrieve only token, another informations are filled blank) by this solution.
//fetch method on frontend
try{
const res = await fetch(`${base_url}/accounts/current_user/`,{
method:'GET',
headers:{
Authorization:`JWT ${localStorage.getItem('token')}`
}
})
const data = await res.json();
setUsername(data.username);
console.log(data)
}catch(err){console.log(err)};
//fetch posts on frontend
const getProblems = async() =>{
const res = await fetch(base_url+'/problems/index');
const data = await res.json();
setProblems(data);
}
//views.py on app for auth manipulation
#api_view(['GET'])
def get_current_user(request):
serializer = GetFullUserSerializer(request.user)
print(serializer.data)
return Response(serializer.data)
//settings.py(related to auth, cors):
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
)
}
CORS_ORIGIN_WHITELIST = (
'http://localhost:3000',
)
CORS_ALLOW_CREDENTIALS = True
JWT_AUTH = {
'JWT_RESPONSE_PAYLOAD_HANDLER':
'new_sns.utils.custom_jwt_response_handler',
}
I have another files such serializer.py, urls.py,but they are absolutely same as the article I extracted.
I guess I misunderstand something around configuration. I would like to hear some of suggestions. Please let me know if you think if there may be problems on another files which I didn't attach on here.
Thanks.
Try changing JWT ${localStorage.getItem('token')} to Authorization:`Bearer ${localStorage.getItem('token')}.
In case this won't work, try djangorestframework-simplejwt - package recommended on DRF's docs.
JSON Web Token is a fairly new standard which can be used for
token-based authentication. Unlike the built-in TokenAuthentication
scheme, JWT Authentication doesn't need to use a database to validate
a token. A package for JWT authentication is
djangorestframework-simplejwt which provides some features as well as
a pluggable token blacklist app.
django-api-logger and axios-jwt may also come handy.
Related
I've had csrf protection with the csurf module working for a while now on my React SPA. I am also using passport for authentication. I do not do any server-side rendering, so the server sends a csrf token in the response body to the client when it hits the /users/current endpoint, which is protected with csrfProtection, something like this:
import csrf from 'csurf';
const csrfProtection = csrf();
router.get("users/current", csrfProtection, async function(req, res)
{
.....
res.write(JSON.stringify({ ..., csrfToken: req.csrfToken() }));
res.end();
}
On the client side I then add the token to all subsequent request headers, a bit like this:
axiosInstance.get("/users/current")
.then(resJson =>
{
axiosInstance.interceptors.request.use(config =>
{
config.headers["x-csrf-token"] = resJson.data.csrfToken;
return config;
});
}
My first question is how the first request even manages to pass the csrfProtection without a token in its header. Yet since the token can only be accessed on the server to send to the client if the route is csrf protected, I don't see a way around this, and it does work somehow.
However, recently I have been getting "ForbiddenError: invalid csrf token" when a user logs in or deletes their account. This has only started happening after I upgraded all my node packages to the latest versions. First the client makes a request to /users/login to submit the username & password, and then makes a request to /users/current to get the new csrf token:
axiosInstance.post("/users/login", {
"username": login.username,
"password": login.password
})
.then(async resJson =>
{
// *code to update user details in redux store*
// ......
axiosInstance.interceptors.request.use(config =>
{
config.headers["x-csrf-token"] = undefined;
return config;
});
return resJson;
})
.then(async resJson =>
{
const { csrfToken } = await axiosInstance.get("/users/current")
.then(resJson => resJson.data);
axiosInstance.interceptors.request.use(config =>
{
config.headers["x-csrf-token"] = csrfToken;
return config;
});
return resJson.data;
}
I suspect it's something to do with subsequent requests coming from a different userId (which I obtain from req.user[0].userId), with which csurf will not accept the previously issued token. But I have no idea how to issue the new token csurf does expect, to the client. And it still doesn't explain why what I had before has suddenly stopped working since none of my logic has changed. This isn't the kind of error I'd typically expect after package updates.
Here someone mentions you can just set any header on the client and have the server check for that. atm I am adding the csrf token to all the client's request headers and using the csurf module's request handler function to check it, but there is nothing stopping me from writing my own. If this is true, the value of the header doesn't even matter, just that it exists. I am holding off on this option though because I feel there is something basic I'm not understanding about my current setup, which once rectified will mean this can be easily fixed.
Would appreciate any help or explanation! Thanks 🤍
We are integrating DRF (dj_rest_auth) and allauth with the frontend application based on React. Recently, the social login was added to handle login through LinkedIn, Facebook, Google and GitHub. Everything was working good on localhost with each of the providers. After the staging deployment, I updated the secrets and social applications for a new domain. Generating the URL for social login works fine, the user gets redirected to the provider login page and allowed access to login to our application, but after being redirected back to the frontend page responsible for logging in - it results in an error: (example for LinkedIn, happens for all of the providers)
allauth.socialaccount.providers.oauth2.client.OAuth2Error:
Error retrieving access token:
b'{"error":"invalid_redirect_uri","error_description":"Unable to retrieve access token: appid/redirect uri/code verifier does not match authorization code. Or authorization code expired. Or external member binding exists"}'
Our flow is:
go to frontend page -> click on provider's icon ->
redirect to {BACKEND_URL}/rest-auth/linkedin/url/ to make it a POST request (user submits the form) ->
login on provider's page ->
go back to our frontend page {frontend}/social-auth?source=linkedin&code={the code we are sending to rest-auth/$provider$ endpoint}&state={state}->
confirm the code & show the profile completion page
The adapter definition (same for every provider):
class LinkedInLogin(SocialLoginView):
adapter_class = LinkedInOAuth2Adapter
client_class = OAuth2Client
#property
def callback_url(self):
return self.request.build_absolute_uri(reverse('linkedin_oauth2_callback'))
Callback definition:
def linkedin_callback(request):
params = urllib.parse.urlencode(request.GET)
return redirect(f'{settings.HTTP_PROTOCOL}://{settings.FRONTEND_HOST}/social-auth?source=linkedin&{params}')
URLs:
path('rest-auth/linkedin/', LinkedInLogin.as_view(), name='linkedin_oauth2_callback'),
path('rest-auth/linkedin/callback/', linkedin_callback, name='linkedin_oauth2_callback'),
path('rest-auth/linkedin/url/', linkedin_views.oauth2_login),
Frontend call to send the access_token/code:
const handleSocialLogin = () => {
postSocialAuth({
code: decodeURIComponent(codeOrAccessToken),
provider: provider
}).then(response => {
if (!response.error) return history.push(`/complete-profile?source=${provider}`);
NotificationManager.error(
`There was an error while trying to log you in via ${provider}`,
"Error",
3000
);
return history.push("/login");
}).catch(_error => {
NotificationManager.error(
`There was an error while trying to log you in via ${provider}`,
"Error",
3000
);
return history.push("/login");
});
}
Mutation:
const postSocialUserAuth = builder => builder.mutation({
query: (data) => {
const payload = {
code: data?.code,
};
return {
url: `${API_BASE_URL}/rest-auth/${data?.provider}/`,
method: 'POST',
body: payload,
}
}
Callback URLs and client credentials are set for the staging environment both in our admin panel (Django) and provider's panel (i.e. developers.linkedin.com)
Again - everything from this setup is working ok in the local environment.
IMPORTANT
We are using two different domains for the backend and frontend - frontend has a different domain than a backend
The solution was to completely change the callback URL generation
For anyone looking for a solution in the future:
class LinkedInLogin(SocialLoginView):
adapter_class = CustomAdapterLinkedin
client_class = OAuth2Client
#property
def callback_url(self):
callback_url = reverse('linkedin_oauth2_callback')
site = Site.objects.get_current()
return f"{settings.HTTP_PROTOCOL}://{site}{callback_url}"
Custom adapter:
class CustomAdapterLinkedin(LinkedInOAuth2Adapter):
def get_callback_url(self, request, app):
callback_url = reverse(provider_id + "_callback")
site = Site.objects.get_current()
return f"{settings.HTTP_PROTOCOL}://{site}{callback_url}"
It is important to change your routes therefore for URL generation:
path('rest-auth/linkedin/url/', OAuth2LoginView.adapter_view(CustomAdapterLinkedin))
I am leaving this open since I think this is not expected behaviour.
I have implemented JWT for user login in my app (before Spotify Auth), like so:
Flask
#auth_blueprint.route('/auth/login', methods=['POST'])
def login_user():
# get post data
post_data = request.get_json()
response_object = {
'status': 'fail',
'message': 'Invalid payload.'
}
if not post_data:
return jsonify(response_object), 400
email = post_data.get('email')
password = post_data.get('password')
try:
# fetch the user data
user = User.query.filter_by(email=email).first()
if user and bcrypt.check_password_hash(user.password, password):
auth_token = user.encode_auth_token(user.id)
if auth_token:
response_object['status'] = 'success'
response_object['message'] = 'Successfully logged in.'
response_object['auth_token'] = auth_token.decode()
return jsonify(response_object), 200
else:
response_object['message'] = 'User does not exist.'
return jsonify(response_object), 404
except Exception:
response_object['message'] = 'Try again.'
return jsonify(response_object), 500
These are the methods of my SQLAlchemy User(db.Model)
def encode_auth_token(self, user_id):
"""Generates the auth token"""
try:
payload = {
'exp': datetime.datetime.utcnow() + datetime.timedelta(
days=current_app.config.get('TOKEN_EXPIRATION_DAYS'),
seconds=current_app.config.get('TOKEN_EXPIRATION_SECONDS')
),
'iat': datetime.datetime.utcnow(),
'sub': user_id
}
return jwt.encode(
payload,
current_app.config.get('SECRET_KEY'),
algorithm='HS256'
)
except Exception as e:
return e
#staticmethod
def decode_auth_token(auth_token):
"""
Decodes the auth token - :param auth_token: - :return: integer|string
"""
try:
payload = jwt.decode(
auth_token, current_app.config.get('SECRET_KEY'))
return payload['sub']
except jwt.ExpiredSignatureError:
return 'Signature expired. Please log in again.'
except jwt.InvalidTokenError:
return 'Invalid token. Please log in again.'
React
App.jsx
loginUser(token) {
window.localStorage.setItem('authToken', token);
this.setState({ isAuthenticated: true });
this.getUsers();
this.createMessage('Welcome', 'success');
};
(...)
<Route exact path='/login' render={() => (
<Form
isAuthenticated={this.state.isAuthenticated}
loginUser={this.loginUser}
/>
)} />
and
Form.jsx
handleUserFormSubmit(event) {
event.preventDefault();
const data = {
email: this.state.formData.email,
password: this.state.formData.password
};
const url = `${process.env.REACT_APP_WEB_SERVICE_URL}/auth/${formType.toLowerCase()}`;
axios.post(url, data)
.then((res) => {
this.props.loginUser(res.data.auth_token);
})
Third Party Authorization + Second App Authentication
Now I'd like to add a second layer of authentication and handle tokens after Spotify callback, like so:
#spotify_auth_bp.route("/callback", methods=['GET', 'POST'])
def spotify_callback():
# Auth Step 4: Requests refresh and access tokens
SPOTIFY_TOKEN_URL = "https://accounts.spotify.com/api/token"
CLIENT_ID = os.environ.get('SPOTIPY_CLIENT_ID')
CLIENT_SECRET = os.environ.get('SPOTIPY_CLIENT_SECRET')
REDIRECT_URI = os.environ.get('SPOTIPY_REDIRECT_URI')
auth_token = request.args['code']
code_payload = {
"grant_type": "authorization_code",
"code": auth_token,
"redirect_uri": REDIRECT_URI,
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET,
}
post_request = requests.post(SPOTIFY_TOKEN_URL, data=code_payload)
# Auth Step 5: Tokens are Returned to Application
response_data = json.loads(post_request.text)
access_token = response_data["access_token"]
refresh_token = response_data["refresh_token"]
token_type = response_data["token_type"]
expires_in = response_data["expires_in"]
# At this point, there is to generate a custom token for the frontend
# Either a self-contained signed JWT or a random token?
# In case the token is not a JWT, it should be stored in the session (in case of a stateful API)
# or in the database (in case of a stateless API)
# In case of a JWT, the authenticity can be tested by the backend with the signature so it doesn't need to be stored at all?
res = make_response(redirect('http://localhost/about', code=302))
return res
Note: this a possible endpoint for getting new Spotify tokens:
#spotify_auth_bp.route("/refresh_token", methods=['GET', 'POST'])
def refresh_token():
SPOTIFY_TOKEN_URL = "https://accounts.spotify.com/api/token"
CLIENT_ID = os.environ.get('SPOTIPY_CLIENT_ID')
CLIENT_SECRET = os.environ.get('SPOTIPY_CLIENT_SECRET')
code_payload = {
"grant_type": "refresh_token",
"refresh_token": refresh_token,
}
encode = 'application/x-www-form-urlencoded'
auth = base64.b64encode("{}:{}".format(CLIENT_ID, CLIENT_SECRET).encode())
headers = {"Content-Type" : encode, "Authorization" : "Basic {}".format(auth)}
post_request = requests.post(SPOTIFY_TOKEN_URL, data=code_payload, headers=headers)
response_data = json.loads(post_request.text)
access_token = response_data["access_token"]
refresh_token = response_data["refresh_token"]
token_type = response_data["token_type"]
expires_in = response_data["expires_in"]
return access_token
What is the best way of handling my tokens after Spotify callback?
Considering that, once user is logged with the app, he will also be logged with Spotify non-stop, having to refresh Spotify's access token every 60 minutes:
Is Authorization Code a server-to-server flow only to protect secret app credentials, and then it is safe to have tokens at frontend?
Should I keep both Access token and refresh tokens stored at frontend, and have a Stateless JWT?
Should I keep only temporary access token and keep refresh tokens at database, having a Stateful JWT?
Should I opt for a Session, persisted only server-side, instead?
What is the safest way of handling my sensitive data here? And, considering the code above, how so?
A huge number of questions here! Let's take them one by one:
Is Authorization Code a server-to-server flow only to protect secret app credentials, and then it is safe to have tokens at frontend?
In the Authorization Code grant, you have to exchange the Authorization Code for a token. This is done with a request to /token (grant_type: authorization_code) and it requires your client_id and client_secret which is secretly stored in your server (aka not-public in your react web app). In this context it's indeed server-to-server.
Should I keep both Access token and refresh tokens stored at frontend, and have a Stateless JWT?
In your case, I would say no. If the token will be used to do some API request to Spotify on server-side, please keep access_token and refresh_token server-side.
But then, it's not anymore stateless ? Indeed.
What could you do "stateless" ?
If you really want/need stateless tokens, IMHO you could store the access_token in a Cookie with following options (and it's mandatory):
Secure: cookies only sent on HTTPS
HttpOnly: not accessible from Javascript
SameSite: preferrably strict! (here it depends if you need CORS)
PRO:
It's stateless
CON:
It might be a huge cookie.
Anyone which access your computer can get the access_token, just like a session cookie. Expiration time is important here. See also: https://stackoverflow.com/a/41076836/2437450
Something else ???? To be challenged.
The case of refresh_token.
I would recommend to store refresh tokens server-side because it's usually a long-life token.
What to do when the access_token expire ?
When a request comes with an expired access_token, you can simply refresh the access_token with server-side-stored refresh_token, do the job, and return the response with a new access_token stored through Set-Cookie header.
Additional note about JWT
If you always have JWT and you store them in Http-Only cookies, you'll probably say that you don't have any way to know if your are logged-in from your React app.
Well there is a trick I already experimented with JWT which is pretty nice.
A JWT is composed of 3 parts; the header, the payload and the signature. What you actually want to protect in your cookies is the signature. Indeed, if you don't have the right signature the JWT is useless. So what you could do is to split the JWT and make only the signature Http-Only.
In your case it should look like:
#app.route('/callback')
def callback():
# (...)
access_token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiIsIm5hbWUiOiJSYXBoYWVsIE1lZGFlciJ9.V5exVQ92sZRwRxKeOFxqb4DzWaMTnKu-VmhW-r1pg8E'
a11n_h, a11n_d, a11n_s = access_token.split('.')
response = redirect('http://localhost/about', 302)
response.set_cookie('a11n.h', a11n_h, secure=True)
response.set_cookie('a11n.d', a11n_d, secure=True)
response.set_cookie('a11n.s', a11n_s, secure=True, httponly=True)
return response
You would have 3 cookies:
a11n.h: the header (options: Secure)
a11n.d: the payload (options: Secure)
a11n.s: the signature (options: Secure, Http-Only)
The consequence is:
a11n.d cookie is accessible from your React app (you can even get userinfo from it)
a11n.s cookie is not accessible from Javascript
You have to reassemble the access_token from cookies on server-side before sending request to Spotify
To reassemble the access_token:
#app.route('/resource')
def resource():
a11n_h = request.cookies.get('a11n.h')
a11n_d = request.cookies.get('a11n.d')
a11n_s = request.cookies.get('a11n.s')
access_token = a11n_h + '.' + a11n_d + '.' + a11n_s
jwt.decode(access_token, verify=True)
I hope it helps!
Disclaimer:
Code samples need to be improved (error handling, checks, etc). They are only examples to illustrate the flow.
I have a ReactJS app running in browser, which needs access to my backend laravel-passport API server. So, I am in control of all code on both client and server side, and can change it as I please.
In my react app, the user logs in with their username and password, and if this is successful, the app recieves a personal access token which grants access to the users data. If I store this token in local storage, the app can now access this users data by appending the token to outgoing requests.
But I do not want to save the access token in local storage, since this is not secure. How do I do this?
Here is what I have tried:
In the laravel passport documentation, there is a guide on how to automatically store the access token in a cookie. I believe this requires the app to be on the same origin, but I cannot get this to work. When testing locally, I run the app on localhost:4000, but the API is run on my-app.localhost. Could this be a reason why laravel passport does not make a cookie with the token, although they technically both have origin localhost?
OAuth has a page on where to store tokens. I tried the three options for "If backend is present", but they seem to focus on how the authorization flow rather than how to specifically store the token.
Here's the relevant parts of my code (of course, feel free to ask for more if needed):
From my react app:
const tokenData = await axios.post(this.props.backendUrl + '/api/loginToken', { email: 'myEmail', password: 'myPassword' })
console.log('token data: ', tokenData)
const personalAccessToken = tokenData.data.success.token;
var config = {
headers: {
'Authorization': "Bearer " + personalAccessToken
};
const user = await axios.get(this.props.backendUrl + '/api/user', config);
From the controller class ApiController:
public function loginToken()
{
if (Auth::attempt(['email' => request('email'), 'password' => request('password')])) {
$user = Auth::user();
$success['token'] = $user->createToken('MyApp')->accessToken;
return response()->json(['success' => $success], 200);
} else {
return response()->json(['error' => 'Unauthorised'], 401);
}
}
and the loginToken function is called from the /api/loginToken route.
Expected and actual results:
Ideally, I would love to have the token saved in a cookie like in the passport documentation, so I don't even have to attach the token to outgoing requests from the react app, but I'm not sure that this is even possible. Perhaps with third party cookies?
Else, I'd just like to find some way to store the token securely (for example in a cookie?), and then append it to outgoing calls from the react app.
I'm new to React. I have already set up small size web service with Django backend on AWS EB. It has custom user model. And most contents are available after user logged in. It works fine.
When I start to make a mobile app with React Native, I set up the Django Rest into same place with sharing same db models with web service. I have chosen a Token authentication way for it. It works fine with React Native app on mobile. Once users log in through a mobile app, API returns auth Token. After getting Token from API, mobile app interacts with API including Token in JSON header.
During learn and develop React Native mobile app. I enjoyed it very much. So, I want to put small react app into one of my web pages, not writing a whole single page app. At this stage, one problem came to my mind that how my react app gets auth Token without inputting user ID and password again.
I spent hours to find any clue through googling, but failed. Can anyone give me a hint? How react app inside already logged web page interact with Token auth based API without log in again?
I think you can use cookies or localStorage to save your token and then in react part just get this token out of there.
You can use localStorage to set and get the token
ex:
set - > localStorage.setItem('token', 'dac43hh5r3nd23i4hrwe3i2un32u');
get - > localStorage.getItem('token');
For ReactJS, take a look at Where to store token. You can use localStorage or cookies.
Cookie usage:
document.cookie = cookie_name + "=" + value + ";expires=" + expire_date + ";";
PS: The expire date should be on GMT format.
localStorage usage:
// To set the token
localStorage.setItem('token', 'dac43hh5r3nd23i4hrwe3i2un32u');
// To get the token
localStorage.getItem('token');
The Django docs for Session, and read this question, it would help you.
Cookie or localStorage?
I would take cookies. Why? Because you can set expiration date(for automatic deletion, for example), domain, path and other things that can be useful. On the localStorage, you just store the info.
I'd like to leave my answer after I solved in my way through my long research and study. My solution is quite simple.
1. set DRF session authentication enable. Adding some code in setting.py
REST_FRAMEWORK = {
# ...
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
}
2. add 'credentials: "include"' into fetch code to use already logged in session cookie for authentication.
await fetch(API_URL, {
credentials: "include"
})
this solution solved my case.
React you can use any libray for the calling API, ex- axios is one of them. When you do the login first time save that token in the localstorage or session.
Now we have to add that token in header for that you can user the interceptor i.e when we make API call every time interceptor will get call, at that place you can get the token from the local storage or session add the request header.
below is sample code of interceptor.
import axios from 'axios';
import cookie from 'react-cookies';
import * as utils from './utils';
let axios_instance = axios.create();
axios_instance.interceptors.request.use(
(configuration) => {
const config = configuration;
const authToken = cookie.load('authToken');
if (authToken) {
config.headers.Authorization = `Token ${authToken}`;
}
return config;
},
(error) => Promise.reject(error)
);
axios_instance.interceptors.response.use((response) => {
return response;
}, (error) => {
if (error.response.status == 401) {
utils.clearCookie();
window.location.href = '/login';
return;
}
if (error.response.status == 403) {
utils.clearCookie();
window.location.href = '/login';
return;
}
if (error.response.status == 404) {
// window.location.href = '/not-found';
return;
}
if (error.response.status == 500) {
// window.location.href = '/server-error';
return;
}
return Promise.reject(error);
});
export default axios_instance;