How can I authorize using werkzeug when accessed via Javascript? - javascript

I implemented Basic Authorization in Flask.
(references this snippet) It works really well.
def check_auth(username):
user_id = get_user_id(username)
return user_id is not None
def authenticate():
"""Sends a 401 response that enables basic auth"""
return Response(
'Could not verify your access level for that URL.¥n'
'You have to login with proper credentials',
status=401,
headers={'WWW-Authenticate': 'Basic realm="Test"'}
)
def requires_auth(f):
"""Basic authorization decorator
"""
#wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username):
return authenticate()
return f(*args, **kwargs)
return decorated
The problem is that when the application is accessed via Javascript(e.g. postmessage username and password via JS), this basic authorization doesn't work.
If authorization failed, the application should return 401 response to users and stop the application , but the application return nothing to users and the application start to work without authorization.
How can I fix this?
Thanks in advance.

Related

Fetch : authentication credentials were not provided

When I request this, everything is ok and I get the data:
const get_players = async()=>{
const response = await fetch('http://127.0.0.1:8000/player_stats/api/players/')
const data = await response.json()
console.log(data)
}
But when I put permission_classes in views.py, I recive this in the console:
{detail: 'Authentication credentials were not provided.}
I am a beginner in Javascript so I wll hope you can understand.
I dont know how to put the authentication credentials in my fech.
Views.py
class PlayersView(ModelViewSet):
permission_classes = [IsAuthenticated]
serializer_class = PlayersSerializer
queryset = Players.objects.all()
def list(self, request):
queryset = Players.objects.all()
serializer = PlayersSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
queryset = Players.objects.all()
qs = get_object_or_404(queryset, pk=pk)
serializer = PlayersSerializer(qs)
return Response(serializer.data)
Urls.py
router = DefaultRouter()
router.register('players',views.PlayersView,basename='players')
app_name = 'main'
urlpatterns = [
path('',include(router.urls)),
]
Any idea?
The user behind the browser that runs the fetch needs to be authenticated, ie. logged in. There are multiple ways to do it. Refer to:
https://www.django-rest-framework.org/api-guide/authentication/
But basically, SessionAuthentication will use Django's user login system, and if you want access the API without logging in, you can use TokenAuthentication in which case, you need to add an HTTP header to your fetch request.
const get_players = async() =>{
const response = await fetch('http://127.0.0.1:8000/player_stats/api/players/', {
headers: {
'Authorization': `Token ${apiToken}`
}
})
const data = await response.json()
console.log(data)
}
your view class tell us that you cant access this page unless the user is authenticated (logged in), i suggest in case you are using django default auth (session auth) getting the cookie that stored in local storage like this : localStorage.getItem('csrf') and then set the session id:
localStorage.setItem('session_id',res.data.session_id)(this is just a dummy example, i have no idea how you set your session id in the backend) when you receive a response in the frontend, or if you are using token auth (like jwt), you should add to headers : headers: {'Authorization': Token ${apiToken}}
, one of the great jwt modules for drf and that easy to use with different endpoints is drf-simplejwt, more info: https://django-rest-framework-simplejwt.readthedocs.io/en/latest/
on a side note : i would not suggest using django default authentication, since it is hard to deal with, especially on the frontend side

How to Secure Laravel API (sanctum)

I have a laravel application with a login system, only for interns
and
I have a Website which gets and posts data from/to the laravel application with a api
So i want, that only my website can get/post data from/to the api -> laravel app
For now, i've created a login user for the api with email and password. I know thats not the right way to do it.
And with this login credentials the website gets a bearer token from the api (expiration 10 min)
With this bearer token (header of ajax call) the website calls every api request (get/post)
Code:
Ajax call to get Bearer Token:
axios.post(data_source_url + "/auth/token", {'email' : 'api#email.de', 'password' : 'pw1'}).then((res) => {
document.cookie = 'bearer=' + res.data + ';expires=' ...
})
Laravel Api Routes:
Route::post('/auth/token', ['uses'=>'ApiController#getToken'])->name('api.getToken');
Route::get('/get', ['middleware'=>'auth:sanctum', 'uses'=>'Api\ApiController#read']);
Route::post('/send', ['middleware'=>'auth:sanctum', 'uses'=>'Api\ApiController#send']);
so I would like to leave it that way with authorise with a bearer token, but how to send and receive a bearer token from the api the right/secure way, because the way i do it right now with the login is totally insecure
You should not leave open your route to get the token. I've written an article in this matter Laravel 8 REST API
This approach lets you get a token by first logging in with a valid user to the app. For your use case, you just remove the register route and controller, and you will be able to secure it just for your use.
Route
Route::post('/login', [ApiController::class, 'login']);
Controller
public function login(Request $request) {
if (!Auth::attempt($request->only('email', 'password'))) {
return response()->json(['message' => 'Incorrect e-mail or password'], 401);
}
$user = User::where('email', $request['email'])->firstOrFail();
$token = $user->createToken('auth_token')->plainTextToken;
return response()->json([
'access_token' => $token,
'token_type' => 'Bearer',
]);
}
I just realized you are logging in to retrieve the bearer. Relax, headers are encrypted using HTTPS, so it is secure as far as I know.
In order to remove the plaintext user and password in the js call, you need to install dotenv by issuing npm install dotenv --save
Now add to your .env file
MY_API_USER=api#email.de
MY_API_PASSWORD=pw1
Then you need to add to your file
require('dotenv').config();
So you can use the sensitive data this way
axios.post(data_source_url + "/auth/token", {'email' : process.env.MY_API_USER, 'password' : process.env.MY_API_USER}).then((res) => {
document.cookie = 'bearer=' + res.data + ';expires=' ...
})
You won't share your .env file, and be sure to add it to your .gitignore or similar, if it is not there.

Laravel + Angular - Get 401 unauthenticated on 1 GET method

I'm developing a Laravel + Angular app and i'm getting 401 Unauthorized in only 1 GET request.
Here I explain how I developed my authentication and how it work on Backend and Frontend. I wish you can help me.
I use Laravel Sanctum for manage authentication in my app. Here is how I program the backend.
I get users from my BD table:
Note: I have created a separate controller, to separate the authentication functions from the user functions, even so, I have tried to put this function in my AuthController and it has not given me any result.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\User;
class UsersController extends Controller
{
public function getAllUsers()
{
return User::all();
}
}
As I want you to only be able to retrieve all the DB users if you are authenticated, in my api.php file I put the path inside the middleware:
Route::middleware('auth:sanctum')->group(function()
{
Route::post('logout', [\App\Http\Controllers\AuthController::class, 'logout']);
Route::get('getAuthUser', [\App\Http\Controllers\AuthController::class, 'getAuthUser']);
//Admin actions
Route::post('createUser', [\App\Http\Controllers\AuthController::class, 'createUser']);
Route::get('getAllUsers', [\App\Http\Controllers\UsersController::class, 'getAllUsers']);
});
If I make the request from the Postman everything works correctly, if I am not authenticated it gives me an error and if I have previously authenticated it returns all the DB users just as I expected. By the way, I am using cookies to send the jwt to the Frontend.
The problem is when in my Angular app I request my backend with the GET method to retrieve these users and display them in a table. In addition, the code to retrieve the users is within a condition in which it is looking at whether the user is authenticated or not. The truth is that I do not understand what may be happening.
getUsers(): void
{
//Check if user is authenticated
this.http.get('http://localhost:8000/api/getAuthUser', { withCredentials: true }). subscribe(
(res: any) =>
{
Emitters.authEmitter.emit(true);
Emitters.roleEmitter.emit(res.role);
//Get all users
this.http.get('http://127.0.0.1:8000/api/getAllUsers', { withCredentials: true }). subscribe(
res =>
{
this.users = res;
}
)
},
err =>
{
Emitters.authEmitter.emit(false);
Emitters.roleEmitter.emit("none");
alert("You should be authenticated for this.");
}
);
}
The first request that you see above getAuthUser, makes the request to the Backend in the same way as the second request getAllUsers and the first one works perfectly and the second one does not, it is in which I get an err. I call the getUsers() method in the ngInit().
I hope I have explained myself well. Any information you need to know let me know. Thank you.
The solution was in the request that gave the error to change the path of the api, instead of putting 127.0.0.1 putting localhost.

Django Rest Framework returning 403 Forbidden when editing user, but works when not logged in as any user

I am trying to make a PUT request on my user model to edit username, bio etc. using Django Rest w/ React frontend.
When I make the PUT request at the url via the django rest client it works no issues. From the frontend, when I am not logged into any django user I can send the PUT request via AXIOS with no issues.
Once I am logged into any django user, even with superuser permissions, I get 403 Forbidden Error on my PUT request.
Here is my views.py:
class RetrieveUpdateDestroyUser(RetrieveUpdateDestroyAPIView):
serializer_class = UserCreateUpdateSerializer
queryset = CustomUser.objects.all()
lookup_field = 'id'
permission_classes = (AllowAny,)
def update(self, request, *args, **kwargs):
"""
PUT and UPDATE requests handled by this method.
"""
return super().update(request, *args, **kwargs)
In my frontend, this is how I make the PUT request (put request done with axios):
export class UserProxy extends BackendProxy {
updateUser(updatedUser, userID) {
let parameters = `user/${userID}`
return new Promise((resolve, reject) => {
this.putRequest(updatedUser, parameters)
.then(response => { resolve(response) })
.catch(error => {
console.log(error)
reject(error)
})
});
}
}
Just very confused as to why I don't get the 403 Forbidden when I am not logged into a django user, but I do when I'm logged in. I am using Python-Social-Auth also for logins if that matters.
Thanks!
One thing that can cause this is if you are using SessionAuthentication. Anonymous users get "authenticated" early in the auth process. Authenticated users go through an additional check of CSRF. If that fails, a HTTP 403 is thrown.
In my case, I realized I should be using GET, and CSRF does not apply (https://www.django-rest-framework.org/topics/ajax-csrf-cors/#csrf-protection).

How to combine Tornado authentication and AngularJS?

I am using the authentication system provided as an example in the tornado documentation. When I want to combine it with AngularJS - AngularJS complains about a Cross-Origin Request.
How can I combine Tornado's authentication system and AngularJS?
Authentication Handlers
class BaseHandler(tornado.web.RequestHandler):
def get_current_user(self):
user_json = self.get_secure_cookie("my_app")
if not user_json: return None
return tornado.escape.json_decode(user_json)
class AuthGoogleLoginHandler(BaseHandler, tornado.auth.GoogleMixin):
'''
The Google OAuth Handler.
The `AuthGoogleLoginHandler` redirects each accepted user
to the index /.
'''
#gen.coroutine
def get(self):
if self.get_argument("openid.mode", None):
user = yield self.get_authenticated_user()
self.set_secure_cookie("my_app",
tornado.escape.json_encode(user))
self.current_user = user
email = user.get('email')
try:
usr = models.User.objects.get(email=email)
except mongoengine.DoesNotExist as e:
# there is no user with the wished email address
# let's create a new one.
new_user = models.User()
new_user.email = user.get('email')
new_user.first_name = user.get('first_name')
new_user.last_name = user.get('last_name')
new_user.locale = user.get('locale')
new_user.save()
self.redirect(self.get_argument('next', '/'))
return
self.authenticate_redirect()
ProfileHandler
class ProfileHandler(BaseHandler):
'''
returns the username of the current user so that it can be used by the AngularJS Templates
'''
#tornado.web.authenticated
def get(self):
if not self.get_current_user():
response = json.dumps({"userdetails":"my dummy user"})
self.write(response)
# user actually exists
else:
response = json.dumps({"userdetails":self.get_current_user()})
self.write(response)
I think you need to enable a cross origin request, wiki CORS for more info:
class BaseHandler(tornado.web.RequestHandler):
def set_default_headers(self):
self.set_header("Access-Control-Allow-Origin", "http://yoursite.com")
Also, it took me a while to figure this out, but normal sessions don't work when Angular is interacting with a RESTful API. What you want is to send credentials in the HTTP Authorisation header on each request. Check this out:
http://wemadeyoulook.at/en/blog/implementing-basic-http-authentication-http-requests-angular/
Hope that helps a bit!

Categories

Resources