Django: Protect view with HTTP Basic Auth and separate user model? - django

I have a little shop-like app powered by Django (1.7), that needs to process credit cards using external gateway.
I have my own User table for customers. They login via form and auth data is kept within sessions.
Now, payment gateway needs to access my app's predefined urls via SSL & Basic Auth.
How I can protect this (and only this) url with HTTP Basic Auth? Also I don't want to add the gateway user in my User table. Also, I don't want my custommers access that secret URL with their credentials (of course, the URL is not displayed to users, but you should never trust the users, right?)
I could to that with HTTP-server (nginx/apache) settings, but then, I can't test this behavior via django test framework.
Is there a solution for this?

You can manually check the request.META['REMOTE_USER'] and request.META['HTTP_AUTHORIZATION'] against your custom credentials, wherever those credentials reside. If the request isn't properly authorized, you need to send back a HTTP 401 Unauthorized response:
def some_view(request, **kwargs):
if not remote_user_is_authenticated(...):
response = HttpResponse()
response.status_code = 401
response['WWW-Authenticate'] = 'Basic realm="%s"' % realm
return response
If you're using Apache with mod_wsgi, the REMOTE_USER Meta header won't be passed on to your application by default; you'll need to put WSGIPassAuthorization On in your Apache config file. Nginx probably has a similar setting (or not, I really don't know :P).

Related

Django REST framework - prevent data access for user view?

In my api, I have a /users endpoint which currently shows (eg address) details of all users currently registered. This needs to be accessed by the (Ember) application (eg to view a user shipping address) but for obvious reasons I can't allow anyone to be able to view the data (whether that be via the browsable api or as plain JSON if we restrict a view to just use the JSONRenderer). I don't think I can use authentication and permissions, since the application needs to log a user in from the front end app (I am using token based authentication) in the first instance. If I use authentication on the user view in Django for instance, I am unable to login from Ember.
Am I missing something?
UPDATE
Hi, I wanted to come back on this.
For authentication on the Ember side I'm using Ember Simple Auth and token based authentication in Django. All is working fine - I can log into the Ember app, and have access to the token.
What I need to be able to do is to access the user; for this I followed the code sample here https://github.com/simplabs/ember-simple-auth/blob/master/guides/managing-current-user.md
I have tested the token based authentication in Postman, using the token for my logged in user - and can access the /users endpoint. (This is returning all users - what I want is for only the user for whom I have the token to be returned but that's for later!).
The question is how to do I pass the (token) header in any Ember requests, eg
this.store.findAll('user') .... etc
This is clearly not happening currently, and I'm not sure how to fix this.
UPDATE
Fixed it. Turns out that the authorize function in my application adapter was not setting the headers, so have changed the code to set the headers explicitly:
authorize(xhr) {
let { access_token } = this.get('session.data.authenticated');
if (isPresent(access_token)) {
xhr.setRequestHeader('Authorization', `Token ${access_token}`);
}
},
headers: computed('session.data.authenticated.token', function () {
const headers = {};
if (this.session.isAuthenticated) {
headers['Authorization'] = `Token ${this.session.data.authenticated.token}`
}
return headers;
})
Ember is framework for creating SPAs. These run in the browser. So for Ember to get the data, you have to send the data to the browser.
The browser is completely under the control of the user. The browser is software that works for them, not for the owner of the website.
Any data you send to the browser, the user can access. Full stop.
If you want to limit which bits of the data the user can read from the API, then you need to write the logic to apply those limits server-side and not depend on the client-side Ember code to filter out the bits you don't want the user to see.
I don't think I can use authentication and permissions, since the application needs to log a user in from the front end app (I am using token based authentication) in the first instance. If I use authentication on the user view in Django for instance, I am unable to login from Ember.
This doesn't really make sense.
Generally, this should happen:
The user enters some credentials into the Ember app
The ember app sends them to an authentication endpoint on the server
The server returns a token
The ember app stores the token
The ember app sends the token when it makes the request for data from the API
The server uses the token to determine which data to send back from the API

Flask JWT Token/ Payload - Return User Json

I understand that Flask JWT gives us the /auth endpoint. Once a user successfully logs in, an access token is assigned and the logged in user can be stored in Flask JWT's current_identity. What I'm wondering is can I also return the User Json back to my client in the same /auth endpoint? Or does it have to be a separate request?
This is for a mobile rest-api, using Flask-Restful. Right now, I have a user log in. The login route (/auth) returns the access token to the client, and then I use the token to get the User Json in a separate request, but I feel like I should be able to condense this into the same request.
Any tips are appreciated :)
IDEA:
Can I create an auth resource via flask-restful and specify exactly what I want it to return? (the token for the server and the user json to the client?)
Flask-JWT has been abandoned for quiet a while now. I would suggest checking out Flask-JWT-Extended instead as an alternative that is still actively maintained (full disclosure, I'm the author of that extension).
In Flask-JWT-Extended you create your own endpoint instead of having the extension create one for you, so you can return whatever data you want there. Here is an example of this in action: http://flask-jwt-extended.readthedocs.io/en/latest/basic_usage.html

Django session on mobile applications

We are currently developing a mobile application(using ionic) for our site that was built using Django. We used django sessions on the site everytime a user logs in. From what I understand, django session sets the session id in the client that is stored in the cookie of the browser. How can we set this session id in the mobile app, if the mobile app is separate from django?
I see at least three ways you can approach this:
find a way to get your ionic app to work with django's cookies (session and CSRF).
change your django app in three ways: 1) have login/logout views that give an authentication token to the mobile application, 2) extend SessionMiddleware to extract the session id from the authentication token sent via the "Authorization" HTTP header of the mobile app's requests, 3) extend django's CSRF middleware to make requests that do not come from web browsers exempt from CSRF checks.
try to use existing solutions for adding JWT (Json Web Token) authentication to Django.
Getting WebView-based hybrid mobile apps to work properly with cookies on both android and ios seems to be non-trivial (e.g. Handling cookies in PhoneGap/Cordova).
The existing solutions for JWT-auth I found thus far don't seem to use Django's sessions (which I find rather convenient for the sake of having a uniform solution for killing the sessions of users who get their phones/computers stolen or their accounts hacked). If someone knows of a pluggable solution that does token-authentication on top of django's regular sessions, please comment.
To elaborate on method 2:
When your mobile app doesn't handle cookies, it can't even log in due to Django's CSRF check. That's why requests coming from your mobile app need to be exempt from CSRF protection.
Note: Do NOT simply disable CSRF protection. While the mobile application isn't vulnerable to CSRF attacks, the browsers of people who are visiting the existing website still are.
To extend the CSRF mechanism to exempt non-browser requests, you need an effective way to determine whether a request is coming from a web browser. There's a bunch of HTTP headers (E.g. Referer, Cookie, X-Requested-With, Origin) we can look at to get an idea. Let's assume for now that your legitimate website users don't spoof their headers.
Use the same "is-web-browser"-check you used for CSRF-exemption to protect the mobile app's login/logout views.
Example Code (for an Android mobile app, iOS headers coming from the mobile app are likely different):
def is_mobile_app_access(request):
return request.META.get('HTTP_REFERER', None) is None
and request.META.get('HTTP_COOKIE', None) is None
and request.META.get('HTTP_X_REQUESTED_WITH', None) == 'your.app.name.here'
and request.META.get('HTTP_ORIGIN', None) == 'file://'
class CustomCsrfViewMiddleware(CsrfViewMiddleware):
def process_view(self, request, callback, callback_args, callback_kwargs):
if is_mobile_app_access(request):
return None
else:
return super(CustomCsrfViewMiddleware, self).process_view(request, callback, callback_args, callback_kwargs)
def process_response(self, request, response):
if is_mobile_app_access(request):
return response
else:
return super(CustomCsrfViewMiddleware, self).process_response(request, response)
class CustomSessionMiddleware(SessionMiddleware):
def process_request(self, request):
if is_mobile_app_access(request):
session_key = request.META.get("HTTP_AUTHORIZATION", None)
else:
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
request.session = self.SessionStore(session_key)
If anyone sees something fundamentally wrong with this approach, please let me know. I suppose the weakest link in this approach is how reliable the is_mobile_app_access check is.
What did you end up doing?

Django-allauth, JWT, Oauth

I have an AngularJS Single Page Application that uses a Django backend API based on the Django Rest Framework. The API is protected via django-rest-framework-jwt. I would like to use django-allauth for account management and authentication on the server side.
I am just missing one single piece in the flow: How does my Oauth-Token from the client get transferred into a JWT-token? Basically, I would like to do as described here http://blog.wizer.fr/2013/11/angularjs-facebook-with-a-django-rest-api/ based on python-social-auth.
So my question is, how do I implement the ObtainAuthToken class from the link with django-allauth?
There are usually two login flows with social login: client-side ("Javascript SDK") and server-side. If your server needs to be authorised, it's usually a lot easier to go through the server-side flow. And that's also what all-auth does I think (and you didn't mention you use a frontend library like the blogpost you mentioned does).
Now the challenge is to provide the token from the server to the frontend. You would probably load the token in the HTML of the initialisation of the SPA, and then from Angular save the token client side (cookie, localStorage, etc.) so the session isn't lost on a refresh.
If you don't want the user to leave your app, you can open your /accounts/login/ or /accounts/signup/ url in a new window. In that new window they authorise your app, and your server receives the token upon return. There, you will have to generate a JWT token manually, and render that into the template so that javascript can access it. With js in that popup window, you can then communicate with your app that opened the popup and pass it the token – see this SO answer for an example – so it can save it.
Django-allauth provides signals that let you hook into the social login process. In your case, I would recommend subscribing to the allauth.socialaccount.signals.pre_social_login signal. The code will look something like this:
from allauth.socialaccount.signals import pre_social_login
#receiver(pre_social_login)
def create_jwt_token(sender, request, sociallogin, **kwargs):
# dig into the sociallogin object to find the new access token.
We used hello.js for O-Auth at the company I worked at.
You provide a shim on the Python end and get the refresh token and whatever other data needed once the user connects their social account.
We redirect them via Django to the page they attempted to access from their OAuth provider's page.
Each user still has their own email account which is needed for the JWT, but you could assume that whatever email is in the scope of their social account is their email then use django's functionality to create new users: User.objects.create(email=emailStringFromOauthData) etc.

How to login a Django account from an iOS App?

In my App I need to communicate with my Django website. Some resources require authentication so I need user login.
But this does not happen in a browser or a web view. I need to use Object-C to issue a login request and handle the response - basically to store the session ID I guess.
On the web server side, how should I do this in Django? To have a stand-alone view for that and return JSON maybe? How can I get the newly generated session ID though?
I wouldn't get the session ID. I believe logging in a user is more geared toward a web interface. I would create an API that serves the resources you need in your app. http://en.wikipedia.org/wiki/Representational_state_transfer Authentication would probably be best suited for a private/public key pair or some other similar popular api authentication system.
You don't need to make any changes to your authentication system, save for maybe making sure the login form is usable on the smaller screen. Cookies work the same on iOS as they do on the web. You can display a modal UIWebView with your login form. After the user logs in, presumably you are setting a session cookie. If you make a subsequent request to the domain the cookie matches, the cookie should be sent along. You want to look into the HTTP 'Accept' header field, which specifies the content type the client expects to receive. In your controller (view?), you'll want to check the 'Accept' header, and return the appropriate content type, probably 'application/json' (or a custom type for your API).