Ive been trying all day to get data from my Asp.Net Api but with no avail. I login and get an authentication token from the server and store it locally but when I try to perform any action that requires authentication, the server returns a 401 response. Is there something Im doing wrong in my code? When I use a tool like postman, everything works okay but not in my app.
This is my login
try {
response = await API.post(AuthUrl, credentials)
if(response.status >= 200 || response.status <= 299){
let Auth = {
Username: response.data.Username,
Roles: response.data.Roles,
Expires: response.data.Expires,
Token: response.data.Token
};
localStorage.setItem(window.location.host, JSON.stringify(Auth));
}
}
This is my axios encapsulator
export default axios.create({
baseURL: BaseUrl,
responseType: "json",
auth: `Bearer ${localStorage.getItem(window.location.host) == null? "" : JSON.parse(localStorage.getItem(window.location.host)).Token}`
})
and this is how im consuming it
try{
const response = await API.get(getUrl)
setLoading(false);
//........Do something with response
}
This is what is logged at the server
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/2.0 GET https://localhost:44307/api/classes/getclasses/
Microsoft.AspNetCore.Cors.Infrastructure.CorsService:Information: CORS policy execution successful.
Microsoft.AspNetCore.Routing.EndpointMiddleware:Information: Executing endpoint 'SchoolManager.Web.Controllers.ClassesController.GetClasses (SchoolManager.Web)'
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Route matched with {action = "GetClasses", controller = "Classes", page = "", area = ""}. Executing controller action with signature System.Collections.Generic.IEnumerable`1[SchoolManager.Dtos.Tenancy.ClassDto] GetClasses(System.String, System.String) on controller SchoolManager.Web.Controllers.ClassesController (SchoolManager.Web).
Microsoft.AspNetCore.Cors.Infrastructure.CorsService:Information: CORS policy execution successful.
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService:Information: Authorization failed.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.
Microsoft.AspNetCore.Mvc.ChallengeResult:Information: Executing ChallengeResult with authentication schemes (Bearer).
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler:Information: AuthenticationScheme: Bearer was challenged.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action SchoolManager.Web.Controllers.ClassesController.GetClasses (SchoolManager.Web) in 146.8824ms
Microsoft.AspNetCore.Routing.EndpointMiddleware:Information: Executed endpoint 'SchoolManager.Web.Controllers.ClassesController.GetClasses (SchoolManager.Web)'
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 218.2724ms 401
The way the axios.create method is used is not right.
Ref: https://github.com/axios/axios#request-config
The documentation clearly shows that config auth: indicates that HTTP Basic auth should be used, and supplies credentials. For Bearer tokens and such, use Authorization custom headers instead so in your case you can do something like this
export default axios.create({
baseURL: BaseUrl,
responseType: "json",
headers: {'Authorization': "bearer " + JSON.parse(localStorage.getItem(window.location.host)).Token}})
Related
There is a server, which serves my client react app at root path. So when I make any request to server from POSTMAN, to login for example, cookies are attached perfect. But when I make request from my client using AXIOS and withCredentials field as well, cookies ain't attached, nevertheless the request is sent good, but no cookies received. I don't think there is any reason to search issues in server code, because postman works with it perfect. In case, there is no CORS errors: server provides client app. I get nice response from the server, with no cookies. Postman gets them.
axios request in react app:
export const login = createAsyncThunk(
'auth/login',
async (credentials: ILogin) => {
// todo: making a request to server
const response = await axios({
url: '/api' + '/auth' + '/login',
method: 'POST',
data: credentials,
withCredentials: true,
headers: {
'Content-Type': 'application/json'
},
});
console.log(response)
}
)
Client doesn't receive cookies, neither on localhost nor deployed app.
As you see, only place where cookies are shown it's network section in devtools, but everything else, including server acts like my second request hadn't any cookie, because in this case, server would answer like: agh, already logged in
P.S: i'm using http
I'm trying to fetch data from the Jira Rest API in my React application by using the Axios library for http requests. An API token is necessary, in order to access data via the Jira API. I generated an API token in my Jira account settings, but I can't figure out, how to include it in my http request to gain access.
This is the endpoint provided by the Jira documentation for getting an issue from the Jira board:
curl -u admin:admin http://localhost:8080/jira/rest/api/2/issue/TEST-10 | python -mjson.tool
This is the React state hook for setting the data to the fetched data:
const [jiraTicket, setJiraTicket] = useState([]);
This is the fetch function for the API request (${} will be filled with user input):
function getJiraTicket() {
axios.get(`${username}:${apiToken}#Content-Type:application/json/https:/${jiraSiteName}.atlassian.net/rest/api/2/issue/${projectKey}-${ticketId}`)
.then((res) => {
const data = res.data;
setJiraTicket(data);
})
}
The button inside the react component return should invoke the fetch function:
return(
<Container>
<Button onClick{getJiraTicket()}>Fetch Jira Ticket</Button>
</Container>
);
This is the error I'm currently getting, because the authorization is not working the way I did it
(I replaced the provided username, API token etc. for this example):
GET http://localhost:3000/username:apitoken#https:/sitename.atlassian.net/rest/api/2/issue/projectkey-ticketid 404 (not found)
Edit:
My current approach:
function getJiraTicket() {
axios.get(`${userName}:${apiToken}#https://${siteName}.atlassian.net/rest/api/2/issue/${projectId}-${ticketId}`,{
auth: {
username: userName,
password: apiToken,
},
withCredentials: true
})
.then((res) => {
const data = res.data;
console.log(data);
setJiraTicket(data);
})
.catch(err => {
// This error means: The request was made and the server responded with a status code
if(err.res) {
console.log(err.res.data);
console.log(err.res.status);
console.log(err.res.headers);
console.log("request was made and server responded with status");
// The request was made but no response was received
} else if (err.request) {
console.log(err.request);
console.log("request was made, but no response was received");
// Something happened in setting up the request that triggered an error
} else {
console.log("Error", err.message);
console.log("request is note set up correctly");
}
console.log(err.config);
})
Current error, which I defined accordingly to the axios doc: "request was made, but no response was received"
Endpoint that works well in Postman (Basic auth is provided in Postman):
https://sitename.atlassian.net/rest/api/2/issue/projectid-ticketid
Update: CORS access isn't allowed, when an application tries to access the Jira API endpoints directly. This restriction takes place in order to prevent random authenticated requests to the specific Jira site, because the access is based on session based authentication. However the API endpoints can be accessed, if OAuth 2.0 is used instead of Basic auth, because the application will redirect the user to the Jira auth itself via this link:
https://auth.atlassian.com/authorize? audience=api.atlassian.com&
client_id=YOUR_CLIENT_ID&
scope=REQUESTED_SCOPE_ONE%20REQUESTED_SCOPE_TWO&
redirect_uri=https://YOUR_APP_CALLBACK_URL&
state=YOUR_USER_BOUND_VALUE& response_type=code& prompt=consent
Source: https://developer.atlassian.com/cloud/jira/platform/oauth-2-3lo-apps/#known-issues
Axios uses a headers config for get/post so you should not include them in your URL. Here is a general example of how you should construct the URL and apply headers:
let axiosUrl = `https://${jiraSiteName}.atlassian.net/rest/api/2/issue/${projectKey}-${ticketId}`
axios({
baseURL: axiosUrl,
method: 'get',
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin", "*"
},
//timeout: 2000,
auth: {
username: userName,
password: apiToken,
}
})
.then((res) => {
setJiraTicket(res.data);
})
.catch(function (error) {
console.log(error);
});
When I try to make a rest call from postman I am able to get access token but using ajax call I am getting 403 forbidden error. Appended https://cors-anywhere.herokuapp.com/ url to access token url to avoid CORS error.
const formData = new FormData();
formData.append("client_id", "client_id");
formData.append("client_secret", "S7D7Q~excS5KjBh9JnPK-afZjTjtALGTKNweP");
formData.append("grant_type", "client_credentials");
formData.append("scope", "https://graph.microsoft.com/.default");
$(document).ready(function () {
requestToken();
});
var token;
function requestToken() {
$.ajax({
async: true,
crossDomain: true,
credentials: "include",
url: "https://cors-anywhere.herokuapp.com/https://login.microsoftonline.com/b262d1f3-4738-400d-ad54-c82cdabb6540/oauth2/v2.0/token",
method: "POST",
headers: {
"content-type": "application/x-www-form-urlencoded"
},
cache: false,
processData: false,
contentType: false,
data: formData,
success: function (response) {
console.log(response);
token = response.access_token;
},
});
}
You should not use a client credential in your front-end. That's your application's password and will be visible to anyone visiting the page. Also if that is your actual secret in the question, you should remove it and create a new one.
This URL will not work:
https://cors-anywhere.herokuapp.com/https://login.microsoftonline.com/b262d1f3-4738-400d-ad54-c82cdabb6540/oauth2/v2.0/token
The reason you get a CORS error is because Azure AD is trying to prevent you from shooting your own foot.
AAD sees the request and thinks it should not come from a browser front-end and thus denies it.
The correct way to get the token is to use MSAL.js and acquire a token on behalf of the signed in user (delegated permissions).
I am building authentication for my application and I am using access and refresh tokens.
Upon user login, the API issues 3 things
refresh token
access token string with headers and payload
access token string with signature
These tokens are all jwt tokens.
This article discusses why access tokens should be split.
using express, I send the tokens back to the browser in my controller like so:
res.cookie(
ACCESS_TOKEN_COOKIE_HEADER_PAYLOAD,
headerAndPayload,
COOKIE_OPTIONS,
)
res.cookie(
ACCESS_TOKEN_COOKIE_SIGNATURE,
signature,
COOKIE_OPTIONS_HTTP_ONLY,
)
res.cookie(REFRESH_TOKEN_COOKIE, refreshToken, COOKIE_OPTIONS)
return res.json({ username, uid })
auth.constants.ts
export const COOKIE_OPTIONS: CookieOptions = {
secure: true,
sameSite: 'lax',
}
export const COOKIE_OPTIONS_HTTP_ONLY: CookieOptions = {
httpOnly: true,
secure: true,
sameSite: 'lax',
}
export const ACCESS_TOKEN_COOKIE_HEADER_PAYLOAD = 'access_token_header_payload'
export const ACCESS_TOKEN_COOKIE_SIGNATURE = 'access_token_signature'
export const REFRESH_TOKEN_COOKIE = 'refresh_token'
In the ui (react) I go into Chrome devtools -> application -> storage -> cookeis and I can see that they are updated everytime I login. This is the behavior I want so that's good so far.
Now when I want to send a request to my API to create something (let's say I am creating a new blog post), I want to grab those cookies and pass them as an Authorization Header.
I am following this person's suggestion except I noticed he is using store which I am guessing is some form of state. Since I am not doing that and multiple sources (source 1, source 2) point to the fact that the standard for sending tokens to the API for authentication is using Authorization header, I would like to follow that.
Currently, when I make an API request using axios, I console log the express request object and can see my tokens in cookies like so:
headers: {
host: 'localhost:3001',
connection: 'keep-alive',
'content-length': '0',
accept: 'application/json, text/plain, */*',
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36',
origin: 'http://localhost:3000',
'sec-fetch-site': 'same-site',
'sec-fetch-mode': 'cors',
'sec-fetch-dest': 'empty',
referer: 'http://localhost:3000/',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'en-US,en;q=0.9',
cookie: 'access_token_header_payload=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InJvc3R5cG9vIiwiaWF0IjoxNTk2ODM0MDIwLCJleHAiOjE1OTY4MzQwODB9; access_token_signature=3pUbxjWgly9xmYSJObOvTgps9qwjOIrHWWE4LPYidmQ; refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InJvc3R5cG9vIiwiaWF0IjoxNTk2ODM0MDIwLCJleHAiOjE1OTc0Mzg4MjB9.IKdRsaTTgAeUfwicLcBpRvw89WgYXy_rCRN5o2BJFqY'
},
but I want to send these cookies as Authorization: Bearer <tokens> instead. How would I do that in axios? Or is what I am doing secure?
this is my axios interceptor
import axios from 'axios'
const service = axios.create({
withCredentials: true,
baseURL: process.env.REACT_APP_API_BASE_URL,
timeout: 5000,
})
// Request interceptors
service.interceptors.request.use(
config => {
return config
},
error => {
return Promise.reject(error)
},
)
// Response interceptors
service.interceptors.response.use(
response => {
console.log('response', response)
return response.data
},
error => {
return Promise.reject({ ...error })
},
)
export default service
HttpOnly means the client script can't access the cookie, as well as you can't read it from document.cookie and pass to axios.
In fact, HttpOnly cookie is more secure than http request headers I think. What you need is parsing the auth cookie in the server side, instead of parsing the request header.
As stated in this response by chinesedfan. The way to authorize your requests to your backend API is through query parsing since your cookie is HttpOnly and can't be accessed by any client.
With express, this can be done by creating a global middleware that sets your authorization header. The following snippet shows how to do this, assuming you are using Bearer <accessToken>.
// global middleware for setting authorization header
app.use((req, res, next) => {
const authHeader = req.cookies.accessToken;
if (authHeader) {
req.headers.authorization = `Bearer ${authHeader}`;
}
next();
});
// initialize passportjs
app.use(passport.initialize())
Add this middleware in your server.js, assuming you named your initializing file this way. This is where you declare your express app variable.
In express, middleware order matters, so add this middleware before you initialize your passport middleware.
In your frontend, you don't have to add anything to axios, just make the request to the backend, and if that request needs authorization, it will be added to you automatically.
I am working on one authentication problem where i have to implement OAuth2.0 authentication for my React App. Is there any way that i can use that authentication with Axios Promise based library???
You will have to pass your Token in the header.
See below;
const instance = axios.create({
baseURL: 'http://localhost/api/',
headers: {'Authorization': 'basic '+ token}
});
instance.get('/path')
.then(response => {
return response.data;
})
OR
Set an Authorization cookie in the browser once you get your token.
The browser will always send the Authorization cookie in each request made to the server. You won't have to pass it through Axios get/post.
UPDATE:
In order to get the access token from the oAuth server, pass the client_id, client_secret, scope and grant_type as follows;
var axios = require("axios");
axios.request({
url: "/oauth/token",
method: "post",
baseURL: "http://sample.oauth.server.com/",
auth: {
username: "myUsername", // This is the client_id
password: "myPassword" // This is the client_secret
},
data: {
"grant_type": "client_credentials",
"scope": "public"
}
}).then(respose => {
console.log(respose);
});
I am assuming that you are using grant_type = "client_credentials", if not, then based on the grant_type you use, you will have to also change the request parameters accordingly.