0

I'm having issues finding the answer anywhere to an issue I'm having related to the (I think) Authorization header in an HTTP request I'm sending from Angular 4 to the Django Rest Framework API I've created. Lets get down to it:

EDIT: Confirmed that the problem relates to authorization since I am also using django-cors-headers now to rid myself of CORS issues for the 4200 port (since it is considered a different origin). The problem that remains is simply that I get the message "Unauthorized" when making requests towards the API. I'm starting to think it is an encoding issue since the following message is shown when I attempt the following request with httpie:

http GET http://localhost:8000/api/groups/ "Authorization: Basic admin:password"

And then the message is shown:

{
    "detail": "Invalid basic header. Credentials not correctly base64 encoded."
}

In settings.py, I've made sure that both a permission class and authentication class have been made available.

REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
    'rest_framework.authentication.BasicAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
    'rest_framework.permissions.IsAdminUser'
],

...

CORS_ORIGIN_WHITELIST = [
    'localhost:4200',
]

In views.py, this is perhaps where my fault is since the console error I get when trying to send the request from Angular -> Rest API is that 'No 'Access-Control-Allow-Origin' header is present on the requested resource.' which is untrue since it does NOT complain about this when I remove the authentication need.

class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all().order_by('-date_joined')
    serializer_class = UserSerializer
    permission_classes = (IsAdminUser, )
    authentication_classes = (BasicAuthentication, )

    def list(self, request):
        queryset = User.objects.all().order_by('-date_joined')
        serializer = UserSerializer(queryset, many=True, context={'request': request})
        return Response(serializer.data,
                        status=200)

The Angular dev server is running on localhost:4200 while django is left on its default of localhost:8000.

I'll include the urls.py for good measure:

from django.conf.urls import url, include
from rest_framework import routers
from backend.api import views
from django.contrib import admin

router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)
router.register(r'info', views.InfoViewSet)

urlpatterns = [
    url(r'^api/', include(router.urls)),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
    url(r'^admin/', admin.site.urls),
]

NOTE that I can make the request and get a response just fine with httpie using the authorization header like so:

http GET http://localhost:8000/api/users/ -a admin:password

Finally, here is the Angular code for making the request (I included everything so that imports can be checked as well):

import { Component, OnInit } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  title = 'app';
  results: string[];

  constructor(private http: HttpClient) {}

  ngOnInit(): void {

    this.http.get(
      'http://localhost:8000/api/users/',
      { headers: new HttpHeaders().set('Authorization', 'Basic admin:password'), }
      ).subscribe(
        data => {
          this.results = data;
      },
        err => {
          console.log("ERROR RETREIVING USERS");
      });
  }
}

I have also imported HttpClientModule and listed it under 'imports' in my app.module.

Måns Thörnvik
  • 1,038
  • 2
  • 10
  • 22

3 Answers3

3

Separate ports are considered different origins, so you will of course, get a Cross-Origin error (CORS) on localhost. This is a browser security feature alongwith your server.

Instal django-cross-header package for resolving cross-domain error.

kmcodes
  • 807
  • 1
  • 8
  • 20
  • 1
    It does not seem to be a CORS issue, I have looked into django-cors-headers. If anything, there is a problem with Authorization but I cannot yet figure out whether it is the way I add the header, if its the django side choice of authentication or something else... I added django-cors-headers for good measure but nothing has changed. – Måns Thörnvik Nov 18 '17 at 17:09
  • [See this](https://stackoverflow.com/questions/43357687/django-python-rest-framework-no-access-control-allow-origin-header-is-present). I had faced similar issues and one of the suggested reasons was that using auth enforces stricter policies by design. I used this and it got resoved. Difference was that we were using Angular 2 and django, but this issue is usually linked with browsers not front end framework. – kmcodes Nov 18 '17 at 17:18
  • Thank you for the help, I think using django-cors-headers solved part of the issue, I just didn't know there were two issues here! I FINALLY got it to work by using your solution AND encoding the Authorization header with a 'btoa' function for the username:password part. – Måns Thörnvik Nov 18 '17 at 17:34
  • You are welcome. If it helped, you can mark the answer as accepted and it can help others with the same issue (and me too :D ). Also describe or post a link if possible to your encoding solution for the angular part. – kmcodes Nov 18 '17 at 17:39
  • I added my own solution as well and explained a bit about why it was happening. It was quite hard to find information and the printouts were less than helpful. But thanks to you and httpie it finally works :D – Måns Thörnvik Nov 18 '17 at 17:51
1

I use the same Backend Framework , and the solution I've found was to build my project with the right host using

ng build --production -host=myDomain.com // try localhost -p 8080 in your case

and replace

Access-Control-Allow-Origin: 'http://localhost:4200'

with

Access-Control-Allow-Origin: '*'
andrea06590
  • 1,259
  • 3
  • 10
  • 22
1

The problem was solved in two ways. Firstly, I did not correctly ensure that CORS would be enabled from the origin of where Angular 4 was sending the request. @kmcodes solution of using django-cors-headers was a good one and I no longer had an issue with 'Access-Control-Allow-Origin' being missing after adding it to my project.

The second part of the problem was in the header I was putting on to the request sent by Angular 4. The following was the change I needed to make:

this.http.get(
      'http://localhost:8000/api/users/',
      { headers: new HttpHeaders().set('Authorization', 'Basic admin:password'), }
      ).subscribe(
        data => {
          this.results = data;
      },
        err => {
          console.log("ERROR RETREIVING USERS");
      });

To this:

this.http.get(
      'http://localhost:8000/api/users/',
      { headers: new HttpHeaders().set('Authorization', 'Basic ' + btoa('admin:password')), }
      ).subscribe(
        data => {
          this.results = data;
      },
        err => {
          console.log("ERROR RETREIVING USERS");
      });

I got the hint when using httpie to inspect my request and saw that when I didn't use the -a flag to add authentication parameters but rather the 'Authorization' header. This gave me an error stating that the request failed with error code 401, unauthorized, since the username:password part was not encoded with base64. In Angular 4 (and maybe before) btoa() solves this as above.

Måns Thörnvik
  • 1,038
  • 2
  • 10
  • 22
  • I also use [requestbin](https://requestb.in/) when I am troubleshooting HTTP requests based issues. – kmcodes Nov 18 '17 at 17:57