In action file the code:
...
const config = {
headers:{
'Content-type': 'application/json'
}
}
const {data} = await axios.post('http://localhost:8000/api/register/',
{'email':email, 'password':password}, config)
...
It's working; then localhost:8000 moved to package.json as a proxy, after that got an issue CSRF token missing or incorrect, how to fix that, thanks.
Application was restarted with no changes. Furthermore, the request has changed to localhost:3000 instead of 8000.
Django can provide the CSRF token in your cookies with a decorator. Then you can get it from your cookies and add it as a HTTP header:
views.py:
from django.views.decorators.csrf import ensure_csrf_cookie
# add this decorator to your main view
# (the one which serves your first html/javascript code, not the /api/register one)
#ensure_csrf_cookie
def index(request):
...
javascript:
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
...
const config = {
headers:{
'Content-type': 'application/json',
'X-CSRFToken': getCookie("csrftoken") // added the csrf cookie header
}
}
const {data} = await axios.post('http://localhost:8000/api/register/',
{'email':email, 'password':password}, config)
Related
I am trying to implement the OAuth authentication for HERE REST API following this documentation: https://developer.here.com/documentation/identity-access-management/dev_guide/topics/sdk.html
My problem is that when I send the POST request with the OAuth Header and Body, the response I receive from the server is: Signature mismatch. Authorization signature or client credential is wrong.
I probed my credentials using the OAuth1.0 authorization in Postman and the HERE API responds with a valid access token. Hence my credentials values are correct.
In addition to the official documentation, I followed recommandations published in various Stack Overflow answers, especially the encoding of all parameters (encodeURI).
I suspect the CryptoJS npm package to be the source of my problem. The result of the HmacSHA256 cryptographic function is very different from various online HmaSHA256 that I tried with my baseString as parameter. However, the crypto-js library (+ #type/crypto-js for Typescript) seems to be the reference for React Typescript crypto functions. I couldn't figure out where I got it wrong :/
Following is my implementation of the OAuth method to get my access token.
I am using :
"#types/crypto-js": "^4.1.1",
"crypto-js": "^4.1.1",
<node.version>v14.14.0</node.version>
<npm.version>v6.14.8</npm.version>
HERE_ACCESS_KEY = <here.access.key>;
HERE_ACCESS_SECRET = <here.access.key.secret>;
HERE_AUTH_URL = "https://account.api.here.com/oauth2/token";
My frontend code:
import CryptoJS from "crypto-js";
private buildOAuthSignatureBaseString(accessKey: string, nonce: string, hashMethod: string, timestamp: string) {
const baseStringParameters = `grant_type=client_credentials&oauth_consumer_key=${accessKey}&oauth_nonce=${nonce}&oauth_signature_method=${hashMethod}&oauth_timestamp=${timestamp}&oauth_version=1.0`;
return `POST&${encodeURIComponent(this.HERE_AUTH_URL)}&${encodeURIComponent(baseStringParameters)}`;
}
private buildHereOauthSignature(accessKey: string, nonce: string, hashMethod: string, timestamp: string) {
const baseString = this.buildOAuthSignatureBaseString(accessKey, nonce, hashMethod, timestamp);
const signingKey = `${encodeURIComponent(this.HERE_ACCESS_SECRET)}&`;
const hashedBaseString = CryptoJS.HmacSHA256(baseString, signingKey);
const base64hash = CryptoJS.enc.Base64.stringify(hashedBaseString);
return encodeURIComponent(base64hash);
}
public getHereMapsApiAccessToken() {
const hashMethod = encodeURIComponent("HMAC-SHA256");
const accessKey = encodeURIComponent(this.HERE_ACCESS_KEY);
const nonce = encodeURIComponent(new Buffer(random(8)).toString("base64"));
const timestamp = Math.floor(new Date().getTime() / 1000).toString(10);
const signature = this.buildHereOauthSignature(accessKey, nonce, hashMethod, timestamp);
const headers = new Headers({
"Content-Type": "application/x-www-form-urlencoded",
"Authorization": `OAuth oauth_consumer_key="${accessKey}", oauth_nonce="${nonce}", oauth_signature_method="${hashMethod}", oauth_timestamp="${timestamp}", oauth_version="1.0", oauth_signature="${signature}"`
});
const method = HTTP_METHODS.POST;
const options = {headers, method, body: JSON.stringify({grant_type: "client_credentials"})};
return fetch("https://account.api.here.com/oauth2/token", options)
.then((response) => {
console.log(response);
return true;
}).catch((error) => {
console.log(error);
return false;
});
}
Thanks in advance !
You can try it from this example https://github.com/alexisad/reactjs_oauth2
Main code is in src/App.jsx:
/**
* Gets an access token for a given access key and secret.
* #param {*} access_key
* #param {*} access_secret
*/
export const getToken = (access_key, access_secret) => {
let url = "https://account.api.here.com/oauth2/token";
let key = encodeURI(access_key);
let secret = encodeURI(access_secret);
let nonce = btoa(Math.random().toString(36)).substring(2, 13);
let timestamp = Math.floor(Date.now()/1000);
let normalizedUrl = encodeURIComponent(url);
let signing_method = encodeURI("HMAC-SHA256");
let sig_string = "oauth_consumer_key="
.concat(key)
.concat("&oauth_nonce=")
.concat(nonce)
.concat("&oauth_signature_method=")
.concat(signing_method)
.concat("&oauth_timestamp=")
.concat(timestamp)
.concat("&").concat("oauth_version=1.0");
let normalised_string = "POST&".concat(normalizedUrl).concat("&").concat(encodeURIComponent(sig_string));
let signingKey = secret.concat("&");
let digest = CryptoJS.HmacSHA256(normalised_string, signingKey);
let signature = CryptoJS.enc.Base64.stringify(digest);
let auth = 'OAuth oauth_consumer_key="'
.concat(key)
.concat('",oauth_signature_method="')
.concat(signing_method)
.concat('",oauth_signature="')
.concat(encodeURIComponent(signature))
.concat('",oauth_timestamp="')
.concat(timestamp)
.concat('",oauth_nonce="')
.concat(nonce)
.concat('",oauth_version="1.0"')
return axios({
method: 'post',
url: url,
data: JSON.stringify({grantType: "client_credentials"}),
headers: {
'Content-Type': "application/json",
'Authorization': auth
}
});
}
/**
* Send request for a given token and url.
* #param {*} token
* #param {*} url
*/
export const sendReqOauth2 = (token, url) => {
return axios({
method: 'get',
url: url,
headers: {
'Authorization': 'Bearer ' + token
}
});
};
I have an application running on http://localhost:8181/ which has React integrated with Spring Boot. (both run on the same port).
I send this POST request to http://localhost:8181/public/authenticate using axios:
The axios instance:
export const axios_register_login_user = axios.create({
baseURL: '/public',
withCredentials: true,
headers: { "content-type": "application/json" },
method: 'POST'
})
The login request:
export async function login(username, password, callback) {
axios_register_login_user.post("/authenticate", {
'username': username,
'password': password
}).then(response => {
console.log("success", response);
callback(response.data);
}).catch(error => {
console.log("failed", error);
callback("Failed");
})
}
The login is successful and I can see a cookie being returned in the response
However, this cookie is not set in the Application->Cookies tab
Here's my API code:
#RequestMapping(value = "/authenticate", method = RequestMethod.POST, produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public ResponseEntity<?> authenticateUser(#RequestBody AuthenticationRequest request, HttpServletResponse response) {
try {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword());
authenticationManager.authenticate(usernamePasswordAuthenticationToken);
} catch (BadCredentialsException e) {
throw new BadCredentialsException("Invalid details");
}
UserDetailsImpl userDetails = (UserDetailsImpl) userDetailsService.loadUserByUsername(request.getUsername());
String name = userDetails.getUser().getName();
String generatedToken = jwtUtil.generateToken(userDetails);
Cookie cookie = new Cookie("jwt", generatedToken);
cookie.setMaxAge(60 * 60 * 10);//like JWT, the max age is 10 hours
// cookie.setSecure(false);
cookie.setHttpOnly(true);
response.addCookie(cookie);
return new ResponseEntity<>(new AuthenticationResponse(name + " " + generatedToken), HttpStatus.OK);
}
I tried adding
#CrossOrigin(allowCredentials = "true", origins = "{http://localhost:3000,http://localhost:8181}")
to the above method but it didn't help.
Please help me out with this. I've been stuck here for 2 days now :(
I was missing cookie.setPath("/") in my code.
Additionally, I also did cookie.setSecure(false);
After these changes, the cookie was added to the browser
I have a spring endpoint that serves a pdf as a byte[] and a React ui that is getting a 406 when I try to call the endpoint.
spring endpoint:
#GetMapping(value = "report/{report_id}", produces = MediaType.APPLICATION_PDF_VALUE)
public ResponseEntity<InputStreamResource> generateReviewTaskReport(
HttpServletResponse response,
#PathVariable("report_id") String reportId,
#RequestAttribute(USER_ID) String loginId) {
byte[] report = reportService.generateReport(reportId, loginId);
ByteArrayInputStream inputStream = new ByteArrayInputStream(report);
HttpHeaders headers = new HttpHeaders();
headers.setContentLength(report.length);
headers.add("Content-Disposition", "inline;filename=" + reportId + "_report.pdf");
return ResponseEntity
.ok()
.headers(headers)
.contentType(MediaType.APPLICATION_PDF)
.body(new InputStreamResource(inputStream));
}
I've tried:
headers.add("Content-Disposition", "attachment;filename=" + reportId + "_report.pdf");
same result.
react request:
export const getReport = (reportId = '') => (dispatch) => {
const report = `${apiConfig.reportUrl}${reportId}`
const promise = axios.get(report,
{
responseType: 'blob',
headers: {
'Accept': 'application/pdf'
}
})
return dispatch({
type: GET_REPORT,
payload: promise,
})
}
case GET_REPORT:
if (payload.data) {
const report = new Blob([payload.data])
reportUrl = URL.createObjectURL(report)
window.open(reportUrl, "_blank")
}
I've tried responseType: 'bufferArray', returning a plain byte[] from my spring endpoint, always get a 406. I'm guessing it's because I have the wrong mime type in my 'Accept' header. I've tried 'application/pdf' and '*/*', same result. What headers do I need to accept an InputStreamResource or byte[]?
With postman I can download the file just fine.
my config:
#Component
public class WebConfiguration extends WebMvcConfigurationSupport {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(byteArrayHttpMessageConverter());
converters.add(new ResourceHttpMessageConverter());
}
#Bean
public HttpMessageConverter byteArrayHttpMessageConverter() {
ByteArrayHttpMessageConverter arrayHttpMessageConverter =
new ByteArrayHttpMessageConverter();
arrayHttpMessageConverter.setSupportedMediaTypes(getSupportedMediaTypes());
return arrayHttpMessageConverter;
}
private List<MediaType> getSupportedMediaTypes() {
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.APPLICATION_PDF);
mediaTypes.add(MediaType.APPLICATION_OCTET_STREAM);
return mediaTypes;
}
}
A general solution, but i think in you'r case it should works fine ;)
axios({
url: 'http://api.dev/file-download', //your url
method: 'GET',
responseType: 'blob', // important
}).then((response) => {
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'file.pdf'); //or any other extension
document.body.appendChild(link);
link.click();
});
gist: https://gist.github.com/javilobo8/097c30a233786be52070986d8cdb1743
Full credits to: https://gist.github.com/javilobo8
In my application I'm trying to send some header parameters from the angular2 application to my node server:
var token = localStorage.getItem('token');
var headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Auth-Token', token);
var url = this.baseUrl + '/initdata';
return this._http.get( url, {headers: headers, body : {}}).toPromise()
.then(response => {
const status = response.json().status;
if(status == SERVER_RESPONSE_STATUS.SUCCESS)
{
return response.json().result;
}
else if( status == SERVER_RESPONSE_STATUS.FAILED)
{
throw new Error(response.json().message);
}
})
.catch(this.handleError);
}
But the problem is when I'm trying to read the value from node, the value for "auth-token" cannot be extracted (saying undefined)
router.use('/', function (req, res, next) {
tokenGenerator.verify(req.header('auth-token'), Constants.AUTH_PRIVATE_KEY, function (err, decoded) {
});
});
in angular2, I'm importing Headers from http as well:
import {Http, Headers} from "#angular/http";
Can someone please help me what's the issue here?
Thanks
You can read tokens from header like this
if(req.headers.hasOwnProperty('token')) {
req.headers.authorization = 'Bearer ' + req.headers.token;
token = req.headers.token;
}
To send the token from Ng2
headers.append('token', token);
This us how I do it.
Is it possible, using the fetch API, to set default headers for every single request?
What I want to do is set an Authorization header whenever there is a json web token in the localStorage. My current solution is to set the headers with this function:
export default function setHeaders(headers) {
if(localStorage.jwt) {
return {
...headers,
'Authorization': `Bearer ${localStorage.jwt}`
}
} else {
return headers;
}
}
Setting the headers in a fetch request would then look like this:
return fetch('/someurl', {
method: 'post',
body: JSON.stringify(data),
headers: setHeaders({
'Content-Type': 'application/json'
})
})
But there has to be a better way to do this. I'm currently developing a React/Redux/Express app if that is of any help.
Creating a fetch wrapper could solve your problem:
function updateOptions(options) {
const update = { ...options };
if (localStorage.jwt) {
update.headers = {
...update.headers,
Authorization: `Bearer ${localStorage.jwt}`,
};
}
return update;
}
export default function fetcher(url, options) {
return fetch(url, updateOptions(options));
}
You also get the added benefit of being able to switch your request client easily for all the calls in your application if you decide you like Axios or other package better. And you can do other things like check if options.body is an object and add the 'Content-Type: application/json header.
You could use Axios instead of fetch, with Interceptors
const setAuthorization = (token) => {
api.interceptors.request.use((config) => {
config.headers.Authorization = 'Bearer ' + token;
return config;
});
}
Where Api is an axios Object with a base URL
const api= axios.create({
baseURL: 'http://exemple.com'
});
And when you get your token, u just have to call the function setAuthorization.
Source: Axios README.md
Andri Möll created a FetchDefaults.js mixin for fetch that sets fetch defaults:
var Url = require("url")
var assign = require("oolong").assign
var merge = require("oolong").merge
var PARSE_QUERY = false
var PROTOCOL_RELATIVE = true // Enable //example.com/models to mimic browsers.
exports = module.exports = function(fetch, rootUrl, defaults) {
if (typeof rootUrl === "string") rootUrl = parseUrl(rootUrl)
else defaults = rootUrl, rootUrl = null
return assign(exports.fetch.bind(null, fetch, rootUrl, defaults), fetch)
}
exports.fetch = function(fetch, rootUrl, defaults, url, opts) {
if (rootUrl != null) url = rootUrl.resolve(url)
if (typeof defaults === "function") defaults = defaults(url, opts)
return fetch(url, opts == null ? defaults : merge({}, defaults, opts))
}
function parseUrl(url) {
return Url.parse(url, PARSE_QUERY, PROTOCOL_RELATIVE)
}
Distributed under AGPL-3.0-only license
A quick and unrecommended hack is to redefine the default .fetch() function:
const oldFetch = window.fetch;
window.fetch = function() {
arguments[1].headers = { 'blahblah' : 'blabla' };
return oldFetch.apply(window, arguments);
}
Code is untested and unfinished. If you decide to use this answer, check arguments.length, add code to preserve existing headers, etc. etc. I'm just providing the direction for further exploration.
You can override default fetch api:
var originalFetch = window.fetch;
window.fetch = function (input, init) {
if (!init) {
init = {};
}
if (!init.headers) {
init.headers = new Headers();
}
// init.headers could be:
// `A Headers object, an object literal,
// or an array of two-item arrays to set request’s headers.`
if (init.headers instanceof Headers) {
init.headers.append('MyHeader', 'Value');
} else if (init.headers instanceof Array) {
init.headers.push(['MyHeader', 'Value']);
} else {
// object ?
init.headers['MyHeader'] = 'Value';
}
return originalFetch(input, init);
};
References:
https://fetch.spec.whatwg.org/#fetch-method
https://fetch.spec.whatwg.org/#requestinit