I am using django authentication, I want to use angular router guards when not signed in. So that it reroutes to login page if not logged in.
I have tried to setup as angular usually would with router guards, but this routes to the url without a trailing slash which doesn't work with Django. I have fixed it so it keeps the trailing slash, but this doesn't route to the Django page, it seems its looking for a Angular page. But if it type in the url for the Django login page that still works.
Auth Guard:
checkLogin(url: string): boolean {
if (this.authService.isLoggedIn) { return true; }
this.authService.redirectUrl = url;
this.router.navigate(['/accounts/login/.']);
return false;
}
app-routing module:
{
path:"",
component: ProjectHomeComponent,
canActivate : [AuthGuard],
children: [
{
path: '',
children: [
{ path: 'view', component: ProjectViewComponent },
{ path: 'seeManage', component: ProjectManageComponent },
{ path: '**', component: PagenotfoundComponent }
]
}
]
}
Expect to be routed to django login page, not routed to django login page
Related
Using:
Django 3.x [ Django-Filters 2.2.0, graphene-django 2.8.0, graphql-relay 2.0.1 ]
Vue 2.x [ Vue-Apollo ]
I am testing single page vue app´s with Django, GraphQL & Vue-Apollo.
If i use csrf_exempt on my view everything works in the frontend.
urlpatterns = [
<...>
path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=True))),
<...>
Now i wanted to CSRF protect my request.
Within the process of understanding the CSRF protection, i thought all Django GraphQLView needs is to receive the "value" of the X-Csrftoken in the Request Header. So i focused on sending the csrf Value in different ways...via a single view like this
path('csrf/', views.csrf),
path("graphql", GraphQLView.as_view(graphiql=True)),
or by ensure a cookie with ensure_csrf_cookie
Afterwards in my ApolloClient i fetch thes Value and send him back with the request Header .
This i what Django prints when i send a GraphQL request from a Django-Vue page.
Forbidden (CSRF token missing or incorrect.): /graphql
Parallel i always test with thegraphiql IDE and these requests still working. I also print everytime the info.context.headers value of my query resolver.
{'Content-Length': '400', 'Content-Type': 'application/json',
'Host': 'localhost:7000', 'Connection': 'keep-alive',
'Pragma': 'no-cache', 'Cache-Control': 'no-cache',
'Accept': 'application/json', 'Sec-Fetch-Dest': 'empty', 'X-Csrftoken': 'dvMXuYfAXowxRGtwSVYQmpNcpGrLSR7RuUnc4IbIarjljxACtaozy3Jgp3YOkMGz',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36',
'Origin': 'http://localhost:7000',
'Sec-Fetch-Site': 'same-origin', 'Sec-Fetch-Mode': 'cors',
'Referer': 'http://localhost:7000/graphql', 'Accept-Encoding': 'gzip, deflate, br', 'Accept-Language': 'en-US,en;q=0.9,de;q=0.8',
'Cookie': 'sessionid=jqjvjfvg4sjmp7nkeunebqos8c7onhiz; csrftoken=dvMXuYfAXowxRGtwSVYQmpNcpGrLSR7RuUnc4IbIarjljxACtaozy3Jgp3YOkMGz'}
i recognized that the GraphQLView IDE alway puts the X-Csrftoken and the Cookie:..csrftoken. also in the request. if delete the csrftoken-cookie of a GraphQLView IDE before sending the request, i get this
Forbidden (CSRF cookie not set.): /graphql
The IDE shows a long, red report
.... CSRF verification failed. Request aborted.</p>\n\n\n
<p>You are seeing this message because this site requires a CSRF cookie when submitting forms.
This cookie is required for security reasons, to ensure that your browser is not being hijacked by third parties.</p>\n
The Information of the IDE say´s the request needs a CSRF cookie. But all read until now in Forums, Doc´s, was more related to the value itself. Meaning all you need is to send the csrf value within the Header as X-Csrftoken or so and the View would do the magic.
Question
Therefore my Question is:
Do i have to set the X-Csrftoken and the Cookie:..csrftoken at the same time in my ApolloClient to make a request on my django GraphQLView ?
Or is it also possible to simple send only the X-Csrftoken without a csrf-cookie and vice versa?
After long time and a pause to follow the issue, i tried once more and found a solution.
Setup
django 3.1
vue 2.6
vue-apollo 3.0.4 (supports the new Apollo-Client 3)
#apollo/client 3.1.3
Presumption
I am using Vue as a multi app and not single app.
the Webpack DevServer will Hot-Reload while writing my *vue.js files in the Django STATICFILES_DIRS. Django will take the Files from there. works fine
Problem Recap
After revisiting my problem i noticed i have 2 Issue. One was the Browser denied graphQL request because of CORS. And the Second was the CSRF Token.
Solution
To Fix the CORS Issue i noticed that my uri of the Apollo Client was not the same as my Django Dev Server. Instead of http://127.0.0.1:7000/graphql it was set to http://localhost:7000/graphql. I also set the credentials (see vue-apollo.js)
To Fix the CSRF i did 3 things
make sure to send an {% csrf_token %} with the HTML where your Vue/ GraphQL Client app is hooked. So that we can fetch it later.
Install js-cookie for getting the Cookie
set a header in the Apollo Client Constructor with X-CSRFToken in vue-apollo.js
vue-apollo.js
import Vue from 'vue'
// import path for the new Apollo Client 3 and Vue-Apollo
import { ApolloClient, InMemoryCache } from '#apollo/client/core';
import VueApollo from 'vue-apollo'
import Cookies from 'js-cookie'
// Create the apollo client
const apolloClient = new ApolloClient({
// -------------------
// # Required Fields #
// -------------------
// URI - GraphQL Endpoint
uri: 'http://127.0.0.1:7000/graphql',
// Cache
cache: new InMemoryCache(),
// -------------------
// # Optional Fields #
// -------------------
// DevBrowserConsole
connectToDevTools: true,
// Else
credentials: 'same-origin',
headers: {
'X-CSRFToken': Cookies.get('csrftoken')
}
});
// create Vue-Apollo Instance
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
})
// Install the vue plugin
Vue.use(VueApollo)
export default apolloProvider
Vue.config.js
const BundleTracker = require("webpack-bundle-tracker");
// hook your apps
const pages = {
'page_1': {
entry: './src/page_1.js',
chunks: ['chunk-vendors']
},
'page_2': {
entry: './src/page_2.js',
chunks: ['chunk-vendors']
},
}
module.exports = {
pages: pages,
filenameHashing: false,
productionSourceMap: false,
// puplicPath:
// Tells Django where do find the bundle.
publicPath: '/static/',
// outputDir:
// The directory where the production build files will be generated - STATICFILES_DIRS
outputDir: '../dev_static/vue_bundle',
chainWebpack: config => {
config.optimization
.splitChunks({
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "chunk-vendors",
chunks: "all",
priority: 1
},
},
});
// Don´t create Templates because we using Django Templates
Object.keys(pages).forEach(page => {
config.plugins.delete(`html-${page}`);
config.plugins.delete(`preload-${page}`);
config.plugins.delete(`prefetch-${page}`);
})
// create webpack-stats.json.
// This file will describe the bundles produced by this build process.
// used eventually by django-webpack-loader
config
.plugin('BundleTracker')
.use(BundleTracker, [{filename: '/webpack-stats.json'}]);
// added to use ApolloQuery Tag (Apollo Components) see vue-apollo documentation
config.module
.rule('vue')
.use('vue-loader')
.loader('vue-loader')
.tap(options => {
options.transpileOptions = {
transforms: {
dangerousTaggedTemplateString: true,
},
}
return options
})
// This will allows us to reference paths to static
// files within our Vue component as <img src="~__STATIC__/logo.png">
config.resolve.alias
.set('__STATIC__', 'static')
// configure a development server for use in non-production modes,
config.devServer
.public('http://localhost:8080')
.host('localhost')
.port(8080)
.hotOnly(true)
.watchOptions({poll: 1000})
.https(false)
.headers({"Access-Control-Allow-Origin": ["*"]})
// DO have Webpack hash chunk filename
config.output
.chunkFilename("[id].js")
},
devServer: {
writeToDisk: true
}
};
Was running into the same issue.
My application backend is Django with graphene. My frontend is React.
I also had 2 issues:
i didn't use the correct graphql url in my frontend apollo createHttpLink. In my django urls.py, my graphql url had "/" but i didn't put "/" in frontend. Just make sure the urls match exactly.
In addition to setting csrf token to header, you also have to set the csrf in Cookie object otherwise you will get a forbidden error. See [this][https://github.com/graphql-python/graphene-django/issues/786]. When the CSRF_USE_SESSIONS settings variable is set to True post requests can not be made as the request will be rejected. This is because the CSRFTOKEN is not provided, because it will not be stored in a cookie.Django will end up giving this warning: Forbidden (CSRF token missing or incorrect.):
This issue can be prevented by passing the view to csrf_exempt in django's urls.py file.
path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True)))
However a better solution is to store csrf in global cookie in the frontend in addition to setting it in the header like this:
const csrftoken = await getCsrfToken();
const cookies = new Cookies();
cookies.set('csrftoken', csrftoken);
(See my index.js code below for full code).
My index.js file:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './custom.scss'
import allReducers from './reducer';
import {Provider} from 'react-redux';
import {BrowserRouter, Route, Routes} from "react-router-dom";
import {
ApolloClient,
InMemoryCache,
ApolloProvider, from, createHttpLink
} from "#apollo/client";
import {createStore} from "redux";
import {AuthProvider} from "./utils/auth";
import {setContext} from "#apollo/client/link/context";
import {ACCESS_TOKEN_KEY} from "./constants/Constants";
import {onError} from "#apollo/client/link/error";
import Cookies from "universal-cookie/es6";
const store = createStore(allReducers,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
let csrftoken;
async function getCsrfToken() {
if (csrftoken) return csrftoken;
csrftoken = await fetch('http://localhost:8000/csrf/')
.then(response => response.json())
.then(data => data.csrfToken)
return await csrftoken
}
const authMiddleware = setContext(async (req, { headers }) => {
const token = localStorage.getItem(ACCESS_TOKEN_KEY);
const csrftoken = await getCsrfToken();
const cookies = new Cookies();
cookies.set('csrftoken', csrftoken);
return {
headers: {
...headers,
'X-CSRFToken': csrftoken,
Authorization: token ? `Bearer ${token}` : ''
},
};
});
const httpLink = createHttpLink({
uri: 'http://localhost:8000/graphql/',
credentials: 'include'
});
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.map(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
)
if (networkError) console.log(`[Network error]: ${networkError}`)
})
const client2 = new ApolloClient({
uri: 'http://localhost:8000/graphql/',
cache: new InMemoryCache(),
credentials: 'include',
link: from([authMiddleware, errorLink, httpLink])
});
ReactDOM.render(
<Provider store={store}>
<React.StrictMode>
<ApolloProvider client={client2}>
<BrowserRouter>
<AuthProvider>
<Routes>
<Route path="/*" element={<App/>} />
</Routes>
</AuthProvider>
</BrowserRouter>
</ApolloProvider>,
</React.StrictMode></Provider>,
document.getElementById('root')
);
On the backend, here are some of the related files:
urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('graphql/', GraphQLView.as_view(graphiql=True)),
path('csrf/', csrf),
]
settings.py:
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = ["http://localhost:3000", ]
CSRF_TRUSTED_ORIGINS = ["http://localhost:3000", ]
CORS_ALLOW_METHODS = [
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT',
]
CORS_ALLOW_HEADERS = [
"accept",
"accept-encoding",
"authorization",
"content-type",
"dnt",
"origin",
"user-agent",
"x-csrftoken",
"x-requested-with",
]
views.py (returns generated csrf token to frontend)
from django.http import JsonResponse
from django.middleware.csrf import get_token
from django.shortcuts import render
# Create your views here.
def csrf(request):
return JsonResponse({'csrfToken': get_token(request)})
As said in the title, I'm using Django, GraphQL, Apollo and VueJS in my project.
I'm developping it as a SPA (Single Page Application).
Everything works fine, until I hit the F5 button and refresh the page. Indeed, it shows an unknown page. The thing is it is VueRouter that is managing the SPA and it works fine. But when I press F5, that is Django that tries to serve a page for the current URL and since it doesn't know it, it can't serve the appropriate page.
I know I can set the VueRouter 'history' mode, which I did, and add a URL to Django that serves index.html whatever the URL is.
My problem is the following :
When I'm on a particular form view (i.e : a User form view) my URL is the following :
http://localhost:8000/user
Since I'm using GraphQL for my API, the retrieved data is not based on the URL. In fact, that is my VueJS component that says : Hey Apollo, run that GraphQL to retrieve the User I want.
So when I refresh, yes it serves the User form view..but empty.
The question is : How could I solve this ?
For clarification purposes, here are some code samples :
My Django URLs :
# (... other imports here ...)
from .schema import schema
urlpatterns = [
path('admin/', admin.site.urls),
path('graphql', csrf_exempt(GraphQLView.as_view(graphiql=True, schema=schema))), # 'schema' is the main GraphQL schema
path('', TemplateView.as_view(template_name='index.html')),
re_path(r'^.*$', TemplateView.as_view(template_name='index.html')) # I saw that many times to serve the page whatever the URL is when refreshing the page
]
My Vue Router :
export default new Router({
mode: 'history',
routes: [
{ path: '/', name: 'MainApp' },
// ...
{ path: '/users', name: 'UserList', component: UserList },
{ path: '/user/create', name: 'UserFormCreate', component: UserForm, props: true },
{ path: '/user', name: 'UserFormView', component: UserForm, props: true },
{ path: '/user/edit', name: 'UserFormEdit', component: UserForm, props: true },
// Same pattern for other models like 'Group' ...
]
My Example VueJS Component :
<script>
import {
// ...
USER_QUERY,
// ...
} from '../../graphql/base/user.js'
export default {
name: 'UserForm',
props: {
userId: Number,
editing: {
type: Boolean,
default: false
}
},
apollo: {
user: {
query: USER_QUERY,
variables () { return { id: this.userId } },
skip () { return this.userId === undefined },
result ({ data }) {
this.form.username = data.user.username
this.form.firstName = data.user.firstName
this.form.lastName = data.user.lastName
}
}
},
data () {
return {
form: {
username: '',
password: '',
firstName: '',
lastName: ''
},
// ...
}
},
methods: {
// ...
}
I have to mention that I've seen more or less related topics but that doesn't solve my problem.
Thanks in advance for your help !
Edit your route paths to use params. For example:
{ path: '/user/:userId', name: 'UserFormView', component: UserForm, props: true }
Now, the app will interpret any number following the user/ path as a prop called userId. (props: true is important here for using the params as props.)
The only other change you need to make is adjusting your router-links to include the id as well (Ex.: http://localhost:8000/user/1) so that when the page is refreshed, there will be a param to read.
I'm trying to make Http requests using Angular 6. My login call works, but when I try to get use a different call, it tells me I'm not logged in. I think it's because the login isn't valid, but I'm not sure how I can keep it valid for subsequent calls. Here is the code appComponent file:
ngOnInit(): void {
this.data = this.login.getData();
this.farmAccessdata = this.getFarmAccess.farmAccess();
}
And here is the login service:
export class loginService {
base_URL = "..."
login = {
username: username,
password: password
}
constructor(private http: HttpClient) {
}
getData(){
return this.http.post(this.base_URL + "...", JSON.stringify(this.login))
.subscribe(data => {
console.log("We got ", data)
})
}
And the farmaccess service:
export class GetFarmAccessService {
data = {};
baseURL = "..."
constructor(private http: HttpClient) { }
farmAccess(){
return this.http.get(this.baseURL + "...")
.subscribe(data => {
console.log("We got ", data)
})
}
When I run the farmAccess service, it gives me an error saying I'm not logged in. The login framework on the server side is cookie based auth, powered by django user module. How can I fix this? Thanks.
I create a app thet need to implement authentification with email/password on all pages except one page (mobile_messages), where need to authenticate with refresh token.
I extend from JWT authenticator and override authenticate method. So it looks like:
authenticate (credentials, headers) {
return new Ember.RSVP.Promise((resolve, reject) => {
this.makeRequest('/auth/mobile_token', credentials, headers)
.then((response) => {
Ember.run(() => {
try {
const sessionData = this.handleAuthResponse(response)
resolve(sessionData)
} catch (error) {
reject(error)
}
})
}, (xhr) => {
Ember.run(() => { reject(xhr.responseJSON || xhr.responseText) })
})
})
}
On mobile_messages route I try to authenticate in before model hook.
beforeModel (transition) {
const authenticator = 'authenticator:api-token'
return this.get('session').authenticate(authenticator, {api_token: transition.queryParams.api_token}).then(() => {
}, (reason) => {
transition.abort()
this.get('notifications').error('Permission denied.', {
autoClear: true,
clearDuration: 6200
})
})
},
I need to stay on mobile_messages route if authenticate rejected. But when I enter to route with wront token I got next backtrase:
Preparing to transition from '' to 'mobileMessages'
index.js:169 generated -> controller:mobileMessages {fullName:
"controller:mobileMessages"}
index.js:169 generated -> controller:aut`enter code here`henticated
{fullName: "controller:authenticated"}
index.js:169 generated -> controller:loading {fullName:
"controller:loading"}
router.js:300 Intermediate-transitioned into 'authenticated.loading'
index.js:169 generated -> route:messages-faxes {fullName:
"route:messages-faxes"}
router.js:190 Transitioned into 'login'
jquery.js:9600 POST http://localhost:3000/auth/mobile_token 500
(Internal Server Error)
It looks like I was redirected before got response from server. An I can't find who is redirect me from route. I try to check ApplicationRouteMixin but i got that sessionInvalidated method calls only if you click logout button. And sessionAuthenticated after success authentification.
If I push to route correct token, then I first redirect to login page and then sessionAuthenticated fires. After that i redirect to baseURL.
Hot to solve issue with redirection to login page?
Ember Simple Auth uses Mixins to determine the route transition behavior that should happen if a user is authenticated/unauthenticated.
For example, this mixin will not allow the user to stay on the route if they are unauthenticated:
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin, {
// route code
});
What you probably want to use is the UnauthenticatedRouteMixin
This mixin is used to make routes accessible only if the session is
not authenticated (e.g., login and registration routes). It defines a
beforeModel method that aborts the current transition and instead
transitions to the routeIfAlreadyAuthenticated if the session is
authenticated.
Include UnauthenticatedRouteMixin in your routes, which needs to accessed if the session is not validated. For example:
// app/routes/login.js
import UnauthenticatedRouteMixin from 'ember-simple-
auth/mixins/unauthenticated-route-mixin';
export default Ember.Route.extend(UnauthenticatedRouteMixin);
It was an error with loading hook. I make an error with naming routes. I created route with name loading to redirect to messages-faxes route. In this case when before model hook return promise ember generate route:application_loading. In application_loading route I run transition to messages-faxes route which has UnauthenticatedRouteMixin. This mixin see that user is not Authenticated and redirect to loading page.
I am trying to implement navigation in Ionic 2. I have tried with DeepLinking and i got the result, but '#' sign is comming in URL.
When '#' sign will come in URL then Google Analytic will not recognize the website, that's why i have tried to implement navigation in different ways like Angular 2 Routing, that supports both (HTML5 or hash URL style), but unable to implement in Ionic 2.
Ex- http://localhost:8100/#/registration - This one working fine but i want without '#'.
Like http://localhost:8100/registration
Thanks for help
I put in a PR for #ionic/app-scripts 3.2.5 to remedy this:
https://github.com/ionic-team/ionic-app-scripts/pull/1545
In the meantime you can edit some project and dependency files to enable it:
src/app/app.module.ts:
IonicModule.forRoot(MyApp,
{
locationStrategy: 'path'
},
{
links: [
{ component: RegistrationPage, name: 'registration', segment: 'registration' },
{ component: LoginPage, name: 'login', segment: 'login' },
{ component: HomePage, name: 'home', segment: '' }
]
})
src/index.html:
<head>
...
<base href="/" />
...
</head>
node_modules/#ionic/app-scripts/dist/dev-server/http-server.js:
function createHttpServer(config) {
var app = express();
app.set('serveConfig', config);
app.get('/', serveIndex);
app.use('/', express.static(config.wwwDir));
app.use("/" + serve_config_1.LOGGER_DIR, express.static(path.join(__dirname, '..', '..', 'bin'), { maxAge: 31536000 }));
// Lab routes
app.use(serve_config_1.IONIC_LAB_URL + '/static', express.static(path.join(__dirname, '..', '..', 'lab', 'static')));
app.get(serve_config_1.IONIC_LAB_URL, lab_1.LabAppView);
app.get(serve_config_1.IONIC_LAB_URL + '/api/v1/cordova', lab_1.ApiCordovaProject);
app.get(serve_config_1.IONIC_LAB_URL + '/api/v1/app-config', lab_1.ApiPackageJson);
app.get('/cordova.js', servePlatformResource, serveMockCordovaJS);
app.get('/cordova_plugins.js', servePlatformResource);
app.get('/plugins/*', servePlatformResource);
if (config.useProxy) {
setupProxies(app);
}
app.all('/*', serveIndex);
return app;
}
The line app.all('/*', serveIndex); is what will redirect any 404 file or directory not found errors to index.html. The locationStrategy: 'path' setting can then work normally with deeplinks and redirects under these circumstances.
Try to use pathLocationStrategy instead of HashLocationStrategy.
Add this in app.module.ts
import { LocationStrategy,
PathLocationStrategy } from '#angular/common';
...
#NgModule({
...
providers: [
{
provide: LocationStrategy,
useClass: PathLocationStrategy
},
...
Or other way is
IonicModule.forRoot(MyApp, {
locationStrategy: 'path'
})
And make sure to have a valid base href.
So here is the list of things which I did. Hope this helps.
We need to remove # in path of every url because Google Analytics rejects the urls with # in them. In App Module , add {locationStrategy: 'path'} to your App Module as follows :
IonicModule.forRoot(MyApp, {
locationStrategy: 'path'
})
2 .Now # is removed from the url. But when you refresh or directly access the url, this wont work because this is expected behaviour for any SPA. When you refresh the page , server tried to find the page at the location mentioned. As stated by #Parth Ghiya above For eg: if you hit localhost/abc , then server tries to find abc/index.html which actually doesn't exist.So to resolve this , you have wrote configurations on my server i.e to point every request to index.html . I am using node express server to deploy the app. Use the following code to route every request to index.html -
var express = require('express');
var path = require('path')
var app = express();
app.use(express.static(path.resolve(__dirname, "www")));
app.use('/*', function(req, res){
res.sendFile(__dirname+ '/www' + '/index.html');
});
app.set('port', process.env.PORT || 3000);
app.listen(app.get('port'), function() {
console.log("listening to Port", app.get("port"));
});