Why does the browser ask me to log in with ASP.NET Core 3.1 - authentication

I've created a website in ASP.NET Core 3.1, MVC, with API. There are 2 parts to the website. An classic static website (with a home, about, contact page etc) and a SPA app. You need to login to use the SPA application.
I believe my approach to auth is quite 'standard'. (There are no different permissions or roles).
The user logs in, and an HTTP Only cookie is created. They are redirected to the Web API part of the website
Any API calls to the C# Web Api, and the front end reviews the return status code (such as code 200 or 500 etc).
If the return is 401, it will assume the JWT has expired or has never been created. The front end then makes another call to the Web Api to retrieve a new Json Web Token. If the JWT is returned, the program attempts the original request again, with the valid JWT. Otherwise, it deals with the situation by alerting the user about the issue
The ajax code looks like
function toDatabase(type, url, data, successDelegate, failDelegate, errorDelegate, tryAgainIfUnathorized) {
$.ajax({
type: type.toUpperCase(),
url: url,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + bearerToken.get()
},
data: data,
dataType: "json",
success: function (response) {
successDelegate(response);
},
error: function (e) {
if (e.status === 401 && tryAgainIfUnathorized) {
const callback = function () {
toDatabase(type, url, data, successDelegate, failDelegate, errorDelegate, false, false);
};
bearerToken.refresh(callback);//try to get the updated token, then retry the original request
}
else {
if (e.status !== 200)
errorDelegate(e.statusText);
console.log("Error in ajaxCall.js. Expand for call stack:");
console.log(e);
}
}
});
This works fine on my local computer.
The problem is, seemingly randomly and not that often, on my production site, Google Chrome occasionally presents a log in dialog. My code does not create this dialog. I don't even know the javascript to create it :)
I don't understand. If I click cancel, then I can continue as I'd like (meaning I am authenticated).
I read up, and it seems that this happens because the browser detects the 401 and tries to be helpful!
I've tried to get round this issue by returning a 499 instead of a 401 but that caused even more headaches with this code
jwtBearerOptions.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
context.Response.OnStarting(() =>
{
context.Response.StatusCode = 499;
return Task.CompletedTask;
});
return Task.CompletedTask;
}
};
How do I prevent this dialog from showing (or is my approach to using JWT incorrect)

Related

Google Identity Services : How to refresh access_token for Google API after one hour?

I have implemented the new Google Identity Services to get an access_token to call the Youtube API.
I try to use this on an Angular app.
this.tokenClient = google.accounts.oauth2.initTokenClient({
client_id: googleApiClientId,
scope: 'https://www.googleapis.com/auth/youtube.readonly',
callback: (tokenResponse) => {
this.accessToken = tokenResponse.access_token;
},
});
When I call this.tokenClient.requestAccessToken(), I can get an access token and use the Youtube API, that works.
But after one hour, this token expires. I have this error : "Request had invalid authentication credentials."
How can I get the newly refreshed access_token transparently for the user ?
There are two authorization flows for the Google Identity Services (GIS) library:
The implicit flow, which is client-side only and uses .requestAccessToken()
The authorization code flow, which requires a backend (server-side) as well and uses .requestCode()
With the implicit flow (which is what you are using), there are no refresh tokens. It is up to the client to detect tokens aging out and to re-run the token request flow. Here is some sample code from google's examples for how to handle this:
// initialize the client
tokenClient = google.accounts.oauth2.initTokenClient({
client_id: 'YOUR_CLIENT_ID',
scope: 'https://www.googleapis.com/auth/calendar.readonly',
prompt: 'consent',
callback: '', // defined at request time in await/promise scope.
});
// handler for when token expires
async function getToken(err) {
if (err.result.error.code == 401 || (err.result.error.code == 403) &&
(err.result.error.status == "PERMISSION_DENIED")) {
// The access token is missing, invalid, or expired, prompt for user consent to obtain one.
await new Promise((resolve, reject) => {
try {
// Settle this promise in the response callback for requestAccessToken()
tokenClient.callback = (resp) => {
if (resp.error !== undefined) {
reject(resp);
}
// GIS has automatically updated gapi.client with the newly issued access token.
console.log('gapi.client access token: ' + JSON.stringify(gapi.client.getToken()));
resolve(resp);
};
tokenClient.requestAccessToken();
} catch (err) {
console.log(err)
}
});
} else {
// Errors unrelated to authorization: server errors, exceeding quota, bad requests, and so on.
throw new Error(err);
}
}
// make the request
function showEvents() {
// Try to fetch a list of Calendar events. If a valid access token is needed,
// prompt to obtain one and then retry the original request.
gapi.client.calendar.events.list({ 'calendarId': 'primary' })
.then(calendarAPIResponse => console.log(JSON.stringify(calendarAPIResponse)))
.catch(err => getToken(err)) // for authorization errors obtain an access token
.then(retry => gapi.client.calendar.events.list({ 'calendarId': 'primary' }))
.then(calendarAPIResponse => console.log(JSON.stringify(calendarAPIResponse)))
.catch(err => console.log(err)); // cancelled by user, timeout, etc.
}
Unfortunately GIS doesn't handle any of the token refreshing for you the way that GAPI did, so you will probably want to wrap your access in some common retry logic.
The important bits are that the status code will be a 401 or 403 and the status will be PERMISSION_DENIED.
You can see the details of this example here, toggle to the async/await tab to see the full code.
To refresh the access token in a transparent way for the end-user you have to use the Refresh Token, This token will also come in the response to your call.
With this token, you can do a POST call to the URL: https://www.googleapis.com/oauth2/v4/token with the following request body
client_id: <YOUR_CLIENT_ID>
client_secret: <YOUR_CLIENT_SECRET>
refresh_token: <REFRESH_TOKEN_FOR_THE_USER>
grant_type: refresh_token
refresh token never expires so you can use it any number of times. The response will be a JSON like this:
{
"access_token": "your refreshed access token",
"expires_in": 3599,
"scope": "Set of scope which you have given",
"token_type": "Bearer"
}
#victor-navarro's answer is correct, but I think the URL is wrong.
I made a POST call to https://oauth2.googleapis.com/token with a body like this and it worked for me:
client_id: <YOUR_CLIENT_ID>
client_secret: <YOUR_CLIENT_SECRET>
refresh_token: <REFRESH_TOKEN_FOR_THE_USER>
grant_type: refresh_token

Nuxt SSR - i can't check if a user is authenticated

I'm trying to work on a Nuxt SSR frontend that uses a Django backend with Session Authentication.
I would like to have some SSR pages as well as client rendered pages in my frontend, so i'm using Universal mode.
The problem is that i did not find a working approach to check if a user is authenticated before loading a page, so i can't restrict pages to anonymous users. In order to check if a user is authenticated, Django will check if the request's headers contain a cookie, and according to that return if the user is authenticated or not.
Here is what i tried:
1) Middleware
export default async function ({context, redirect}) {
axios.defaults.withCredentials = true;
return axios({
method: 'get',
url: 'http://127.0.0.1:8000/checkAuth',
withCredentials: true,
}).then(function (response) {
//Redirect if user is authenticated
}).catch(function (error) {
console.log(error)
});
}
Here i'm sending a request to my backend to check if the user is authenticated. The problem is that the middleware is executed from server side, which means there will never be any cookie in the request, even if the user is authenticated. This means that every time i refresh the page, according to the middleware the user is always anonymous, even when the user is authenticated.
2) Plugin
export default function (context, inject) {
if (process.client){
console.log('client')
return axios({
method: 'get',
url: 'http://127.0.0.1:8000/checkAuth',
withCredentials: true,
}).then(function (response) {
//IF AUTHENTICATED, REDIRECT
context.redirect('/')
}).catch(function (error) {
console.log(error)
});
} else {
console.log('server')
}
}
Here i'm trying the same but with a plugin, and i'm "forcing" the plugin to check if the user is authenticated on the backend only when the plugin executes from client side. This works, cookies are sent in the headers and Django receives the cookie, but the problem with this solution is that Nuxt doesn't allow redirecting to other pages from a plugin (https://github.com/nuxt/nuxt.js/issues/4491).
3) Using beforeMount() in Vue
I tried to do that using beforeMount() from my Vue pages, but the problem is that since it will execute AFTER idration, the page will be loaded and after 1/2 seconds the redirect happens, which is kind of ugly.
Is it possible that there isn't a way to do this? Any kind of advice is appreciated
EDIT: the problem is not that i don't know how to code this, the problem is that when Nuxt sends a request to my backend from the server side middleware, the request will not contain any cookie, and because of this my Django backend cannot check the session cookie, which means that the backend cannot check whether or not the user is authenticated. The same code works when the middleware is executed from client side (if i navigate directly to the page instead of refreshing), because the request will contain the cookies.
I'm trying to understand if this is normal or not, but this could be an issue with Nuxt.
I know this a year old question and it was probably about nuxt 2, now nuxt 3 is out and running and I found my self with the same problem and here is how I solved it, just in case someone stumble here just like I did.
With Nuxt 3 server side you can use useFetch with the options headers: useRequestHeaders(['cookie'])
const { data, error, pending, refresh } = await useFetch(api.auth,
{
credentials: "include",
headers: useRequestHeaders(['cookie'])
}
);
There are a few issues you need to be aware of:
_ The cache, if you perform the same request with the same parameters it will return the same cached response (it won't even call the end point API). Try to use the key option with different values or the returned refresh method and check the doc "Data fetching" for more info.
_ The cookies, any cookie generate server side won't be shared with the client side, this means if the API generate a new token or session cookie on server side the browser won't receive those cookies and may generate new ones, this may get you in some 400 - bad request if you use session with CSRF, check this issue for more info.
I do have a working middleware with this
export default ({ redirect, store }) => {
if (store?.$auth?.$state?.loggedIn) {
redirect('https://secure.url')
} else {
redirect('https://login.please')
}
})

Using the Rally API to pull user profile

I am trying to use the Rally web service API to get some data. Code as blow. On IE it will pop out a login window, after entry login name and password, I am about to get some data. But when I use chrome, it response 401, not sure what I missing. I know there is SDK available, but due to some limitation, not able to use it. Any suggestions please?
var url = https://rally1.rallydev.com/slm/webservice/v2.0/users;
$.ajax({
url: url,
type: 'GET',
heards: { zsessionid: apiKey },
success: function(json) {
console.log(JSON.stringify(json));
}
},
error: function( req, status, err ) { console.log( 'something went wrong', status, err );
}
});
I'd love to know more about why you can't use the SDK. Anyway, in this case your config likely needs headers instead of heards to pass the api key.

OAuth Post Request Failing

I've got everything working up until Step 2 of the OAuth process where you request the actual token. I'm using a very simple jQuery Post request and constantly getting Access Control Origin errors. I've tried contentType: 'application/json' and everything else I know to try.
It's just not working and I'm not sure the problem. I've confirmed all the variables are set properly before the request. Simple post request...
var url = 'https://[STORENAMEVARIABLE].myshopify.com/admin/oauth/access_token';
var data = JSON.stringify({ client_id: apiKey, client_secret: secret, code: code });
$.ajax({
type: 'POST',
url: url,
data: data,
success: function(data) {
debugger;
},
error: function(data) {
debugger;
}
});
Any ideas what I'm doing wrong?
You need to make your OAuth requests from a server. This is the Javascript cross-domain security kicking in.
If you are using Rails you can use omniAuth and it'll take care of the whole OAuth dance for you. Otherwise you'll have to search around but most popular language have an OAuth library that you can just plug in.

Backbone.js and user authentication

I have been wondering for quite a while how I would go about authenticating users using Backbone because I have been reading a few articles about it and a lot of them are talking about tokens and keys.. But I just want to be able to sign in a user and register a user like you would normally.
I was thinking that on the web app start up there would be a request to the route '/me' and then the server gives the user back appropriate information if he/she is logged in.
Like if the route came back with {loggedIn: false} the backbone router would send the user to the login/register pages only. But if it came back with a users profile information then it would obviously mean he had a session.
Is this an okay way of going back user authentication when using Backbone?
Short answer: wire up $.ajax to respond to 401 (Unauthorized) status codes.
Long answer: We're consuming a RESTful api from a single page website. when the server detects an unauthorized request, it just returns a 401. The client will redirect to /login?#requested/resource.
/login will prompt for authorization (redirect to google's oath server in our case) then add an authorization cookie and redirect to the originally requested #requested/resource
we're also sending the auth cookie on every $.ajax request.
Hopefully this is helpful.
define(
[
'jquery',
'jquery.cookie'
],
function ($) {
var redirectToLogin = function () {
var locationhref = "/login";
if (location.hash && location.hash.length > 0) {
locationhref += "?hash=" + location.hash.substring(1);
}
location.href = locationhref;
};
var $doc = $(document);
$doc.ajaxSend(function (event, xhr) {
var authToken = $.cookie('access_token');
if (authToken) {
xhr.setRequestHeader("Authorization", "Bearer " + authToken);
}
});
$doc.ajaxError(function (event, xhr) {
if (xhr.status == 401)
redirectToLogin();
});
});