Auth0: Validating id_token/JWT on UI (javascript) level - authentication

Update March 2019
I just went over this question again; the Auth0's github code has been updated in December 2018. They are now storing 'access_token','id_token' and 'expire_at' into the object/session, instead of localstorage and using now an 'isLoggedIn' flag to mark if authenticated or not. Check the pull request and these 2 lines in the specific commit: line1 and line2.
If you do not need to re-validate 'id_token' - like I was doing in the original question - that might be an alternative. Otherwise check original question.
Original Question
We are using auth0 for one of our clients. One stack that we are using it for is:
React/Redux UI
NodeJS backend
So we are using a cross origin authentication using implicit grant for that, using JWT with an RS256 algorithm. We also refresh tokens in background using silent authentication.
I was able to validate 'access_token' on the API (nodejs) side using node-jwks-rsa for express
On the UI level, after going through the source code of the auth0-js library I noticed that the "parseHash" method used in their provided react samples, actually validates tokens before we store them in localstorage, ie on successful authentication. Mainly this line in the source code.
Then I used their sample code that allows us to check if a user is authenticated, method isAuthenticated().
Problem with the isAuthenticated() method
From a security perspective, if later on (post authentication) a user of the application decided to manually modify the 'expire_at' label in the storage, they could get away as indeed authenticated. While of course there is additional security checking in our app, I wanted to update this function to validate 'id_token'. So far, I couldn't find any example in auth0's online docs for how to do that.
After digging in their source code I found a method validateToken that is being used. So I decided to leverage it in one of our functions:
import IdTokenVerifier from 'idtoken-verifier'
.... Some code in here ....
reValidateToken() {
return new Promise((resolve, reject) => {
// Both of these are stored in localstorage on successful authentication, using the parseHash method
let id_token = localStorage.getItem('id_token');
let transactionNonce = localStorage.getItem('app_nonce');
this.webAuth.validateToken(id_token, transactionNonce, function(
validationError,
payload
) {
if (!validationError) {
resolve('no validation errors for id_token');
}
if (validationError.error !== 'invalid_token') {
reject(validationError.error);
}
// if it's an invalid_token error, decode the token
var decodedToken = new IdTokenVerifier().decode(id_token);
// if the alg is not HS256, return the raw error
if (decodedToken.header.alg !== 'HS256') {
reject(validationError);
}
});
});
}`
Now, for it to succeed; we store the nonce in localstorage after successful authentication, does this approach create back doors for potential security holes? if it does; what is best practice to validate RS256 JWT id_token(s) on a UI level?

Related

What's behind a REST API

I'm working on the frontend part of some REST API (link, json validation and in generale controlling)
I'm trying to figure out how it works in the backhand, there should be a database I guess and each API call correspond to a specific query?
could you suggest me guide on how such implementation are usually build?
I'm only finding formal guide on how to shape url for rest API
thanks
It is a quite generale / cultural question, not technical
There's no standard way, the point of a protocol like HTTP (what REST is based on) is to decouple this kind of details and leave the server implementor to be free of doing it however it wants.
There are a lot of different ways and listing them all is very hard.
For a simple service what you said is true, for more complex scenario behind a REST endpoint there could be a service doing calls to other services and aggregating their responses into the json you see.
You would usually have a single endpoint do a single task but you could also do anything you want with the data provided from the user. You could carry out regex validations, store it inside a database, send it to another API, extract data out of it, and plenty of other things.
Here is an example I wrote in Node.js:
const signup_post = async (req, res) => {
const { email, password, username } = req.body;
try {
const user = await User.create({ email, password, username });
const token = createToken(user._id);
res.cookie('jwt', token, { httpOnly: true, maxAge: maxAge * 1000 });
res.status(201).json({ user: user._id });
}
catch(err) {
const errors = handleErrors(err);
res.status(400).json({ errors });
}
}
This example code, we are taking the user-provided data, making a new entry in the database with a pre-defined schema, creating a JWT token and attaching it to a cookie, and sending the cookie back to the client. This is one way to handle authentication- and as long as the client has this cookie, they will stay logged in.
We are also handling any errors and validating the user-provided data to make sure it fits our database schema.

Blazor WASM - OpenIdConnect - Disable token validation to identity provider on every page refresh

We are creating a Blazor WASM application for usage on unstable and possibly slow connections. We have successfully implemented authentication with OpenIdConnect.
We noticed that on every refresh (F5) of the page, the token is being validated against the Identity Provider again:
We think this is normal/desired behaviour, but is there any way around this?
We know this is a tiny amount of data, but it would be optimal to not have this every time.
The websites are for 'internal' usage only (through a VPN).
Thank you
I have personally run into this issue as well. For us, it was even worse since the IdP would take quite some time since the authorization endpoint would ignore the prompt=none parameter and try to challenge the user every time Blazor WASM Authentication tried to refresh its authentication state. This forced me to do some digging so hopefully, my findings are useful to you.
The OIDC in Blazor WASM makes use of their RemoteAuthenticationService class which implements the AuthenticationStateProvider to provide an authentication state to Blazor WASM on top of the Access Token.
I think this is the key problem here. That they are separating the AuthState and AccessToken which (at least for me) was unintuitive since in the past I would determine whether a user is "logged in", purely based on if they have a valid access token or not.
So the fact that you already have an "AccessToken" is irrelevant to the AuthState which begs the question: How do they determine your AuthState?
Lets checkout this key function in the RemoteAuthenticationService:
...
public override async Task<AuthenticationState> GetAuthenticationStateAsync() => new AuthenticationState(await GetUser(useCache: true));
...
private async Task<ClaimsPrincipal> GetUser(bool useCache = false)
{
var now = DateTimeOffset.Now;
if (useCache && now < _userLastCheck + _userCacheRefreshInterval)
{
return _cachedUser;
}
_cachedUser = await GetAuthenticatedUser();
_userLastCheck = now;
return _cachedUser;
}
In the above code snippet you can see that the AuthState is determined by this GetUser function which first checks some cache for the user which is currently hardcoded to expire every 60 seconds. This means that if you check the user's AuthState, then every 60 seconds it would have to query the IdP to determine the AuthState. This is how it does that:
Uses JSInterop to call trySilentSignIn on the oidc-client typescript library.
SilentSignIn opens a hidden iframe to the IdP authorization endpoint to see if you are in fact signed in at the IdP. If successful then it reports the signed-in user to the AuthState provider.
The problem here is that this could happen every time you refresh the page or even every 60 seconds whenever you query the current AuthState where the user cache is expired. There is no persistence of the access token or the AuthState in any way.
Ok so then how do I fix this?
The only way I can think of is to implement your own RemoteAuthenticationService with some slight modifications from the one in the Authentication Library.
Specifically to
Potentially persist the access token.
Reimplement the getUser call to check the validity/presence of the persisted access token to get the user rather than using the silentSignin function on the oidc-client library.

Get user info when someone runs Google Apps Script web app as me

I have a standalone Google Apps Script deployed as a web app. The app is executed as me, because I want it to access files stored on my Drive, and because I want it to generate Google Sheets files that have some ranges protected from the user that are still editable by the script. However, I want these files to be segregated into folders, and each folder is assigned to a user, so I need to know who the user is each time the app runs.
Session.getActiveUser().getEmail() doesn't work since the web app is deployed as me and not as the user. My other thought was to make the app "available to everyone, even anonymous" (right now it's just "available to everyone") to skip Google's login screen, and use some kind of third-party authentication service or script. Building my own seems like overkill because this seems like it should already exist, but so far my research has only turned up things like Auth0 which seem incompatible with my simple Google Apps Script-based app, or else I'm too inexperienced to figure out how to use them.
Does anyone have a suggestion for how to authenticate users for this kind of web app? Preferably something that comes with a beginner-friendly tutorial or documentation? Or, is there another way for me to find out who's running the app while still executing it as myself?
I am so new to this I'm not even sure I'm asking this question in the right way, so suggested edits are taken gratefully.
I can think of two ways you might approach this where the Web App is deployed to execute as the user accessing it:
Scenario A: Create a service-account to access files stored on your Drive and to generate google sheets.
Scenario B: Create a separate Apps Script project deployed as an API Executable and call its functions from the main Web App.
These methods are viable but there are a number of pros and cons to each.
Both require OAuth2 authentication, but that bit is fairly easy to handle thanks to Eric Koleda's OAuth2 library.
Also, in both scenarios, you'll need to bind/link your main Apps Script project to a GCP project and enable the appropriate services, in your case Google Sheets and Google Drive APIs (see documentation for more details).
For Scenario A, the service account must be created under the same GCP project. For Scenario B, the secondary Apps Script project for the API executable must also be bound to the same GCP project.
Issues specific to Scenario A
You'll need to share the files and folders you want to access/modify (and/or create content in) with the service account. The service account has it own email address and you can share google drive files/folders with it as you would with any other gmail account.
For newly created content, permissions could be an issue, but thankfully files created under a folder inherit that folder's permissions so you should be good on that front.
However, you'll have to use the REST APIs for Drive and Sheets services directly; calling them via UrlFetch along with the access token (generated using the OAuth2 library) for the Service Account.
Issues specific to Scenario B
You'll need to setup a separate Apps Script project and build out a public API (collection of non-private functions) that can be called by a 3rd party.
Once the script is bound to the same GCP project as the main Web App, you'll need to generate extra OAuth2 credentials from GCP console under the IAM (Identity Access Management) panel.
You'll use the Client ID and Client Secret, to generate a refresh token specific to your account (using the OAuth2 library). Then you'll use this refresh token in your main Web App to generate the requisite access token for the API executable (also using the OAuth2 library). As in the previous scenario, you'll need to use UrlFetch to invoke the methods on the API Executable using the generated access token.
One thing to note, you cannot use triggers within the API executable code as they are not allowed.
Obviously, I've glossed over a lot of the details but that should be enough to get you started.
Best of luck.
Now that I've implemented TheAddonDepot's suggested Scenario B successfully, I wanted to share a few details that might help other newbies.
Here's what the code in my web app project looks like:
function doGet(e) {
// Use user email to identify user folder and pass to var data
var userEmail = Session.getActiveUser().getEmail();
// Check user email against database to fetch user folder name and level of access
var userData = executeAsMe('getUserData', [userEmail]);
console.log(userData);
var appsScriptService = getAppsScriptService();
if (!appsScriptService.hasAccess()) { // This block should only run once, when I authenticate as myself to create the refresh token.
var authorizationUrl = appsScriptService.getAuthorizationUrl();
var htmlOutput = HtmlService.createHtmlOutput('Authorize.');
htmlOutput.setTitle('FMID Authentication');
return htmlOutput;
} else {
var htmlOutput = HtmlService.createHtmlOutputFromFile('Index');
htmlOutput.setTitle('Web App Page Title');
if (userData == 'user not found') {
var data = { "userEmail": userEmail, "userFolder": null };
} else {
var data = { "userEmail": userData[0], "userFolder": userData[1] };
}
return appendDataToHtmlOutput(data, htmlOutput);
}
}
function appendDataToHtmlOutput(data, htmlOutput, idData) { // Passes data from Google Apps Script to HTML via a hidden div with id=idData
if (!idData)
idData = "mydata_htmlservice";
// data is encoded after stringifying to guarantee a safe string that will never conflict with the html
var strAppend = "<div id='" + idData + "' style='display:none;'>" + Utilities.base64Encode(JSON.stringify(data)) + "</div>";
return htmlOutput.append(strAppend);
}
function getAppsScriptService() { // Used to generate script OAuth access token for API call
// See https://github.com/gsuitedevs/apps-script-oauth2 for documentation
// The OAuth2Service class contains the configuration information for a given OAuth2 provider, including its endpoints, client IDs and secrets, etc.
// This information is not persisted to any data store, so you'll need to create this object each time you want to use it.
// Create a new service with the given name. The name will be used when persisting the authorized token, so ensure it is unique within the scope
// of the property store.
return OAuth2.createService('appsScript')
// Set the endpoint URLs, which are the same for all Google services.
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
// Set the client ID and secret, from the Google Developers Console.
.setClientId('[client ID]')
.setClientSecret('[client secret]')
// Set the name of the callback function in the script referenced
// above that should be invoked to complete the OAuth flow.
.setCallbackFunction('authCallback')
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getScriptProperties())
// Enable caching to avoid exhausting PropertiesService quotas
.setCache(CacheService.getScriptCache())
// Set the scopes to request (space-separated for Google services).
.setScope('https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/spreadsheets')
// Requests offline access.
.setParam('access_type', 'offline')
// Consent prompt is required to ensure a refresh token is always
// returned when requesting offline access.
.setParam('prompt', 'consent');
}
function authCallback(request) { // This should only run once, when I authenticate as WF Analyst to create the refresh token.
var appsScriptService = getAppsScriptService();
var isAuthorized = appsScriptService.handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput('Success! You can close this tab.');
} else {
return HtmlService.createHtmlOutput('Denied. You can close this tab.');
}
}
function executeAsMe(functionName, paramsArray) {
try {
console.log('Using Apps Script API to call function ' + functionName.toString() + ' with parameter(s) ' + paramsArray.toString());
var url = '[API URL]';
var payload = JSON.stringify({"function": functionName, "parameters": paramsArray, "devMode": true})
var params = {method:"POST",
headers: {Authorization: 'Bearer ' + getAppsScriptService().getAccessToken()},
payload:payload,
contentType:"application/json",
muteHttpExceptions:true};
var results = UrlFetchApp.fetch(url, params);
var jsonResponse = JSON.parse(results).response;
if (jsonResponse == undefined) {
var jsonResults = undefined;
} else {
var jsonResults = jsonResponse.result;
}
return jsonResults;
} catch(error) {
console.log('error = ' + error);
if (error.toString().indexOf('Timeout') > 0) {
console.log('Throwing new error');
throw new Error('timeout');
} else {
throw new Error('unknown');
}
} finally {
}
}
I generated the OAuth2 credentials at https://console.cloud.google.com/ under APIs & Services > Credentials > Create Credentials > OAuth Client ID, selecting "Web application". I had to add 'https://script.google.com/macros/d/[some long ID]/usercallback' as an authorized redirect URI, but I apologize as I did this two weeks ago and can't remember how I figured out what to use there :/ Anyway, this is where you get the client ID and client secret used in function getAppsScriptService() to generate the access token.
The other main heads up I wanted to leave here for others is that while Google Apps Scripts can run for 6 minutes before timing out, URLFetchApp.fetch() has a 60s timeout, which is a problem when using it to call a script via the API that takes more than 60s to execute. The Apps Script you call will still finish successfully in the background, so you just have to figure out how to handle your timeout error and call a follow-up function to get whatever the original function should have returned. I'm not sure if that makes sense, but here's the question I asked (and answered) on that issue.

socket.io unity authentication

I have this use case:
- I'm working on a game with a webapp for user management and chat, which is on MERN, and a unity game, with socket.io as the real time messaging layer for the multiplayer game.
- User may register to webapp by either providing a pair of email/password, or getting authenticated on FB/Gamil/etc. as usual, in which case the user's email is obtained and saved to MongoDB and this is done by passport.
- There is no session in express side, and socket.io is on a redis. There is no cookie but JWT is used.
My problem is that I don't know what's the best practices in this. I read this
article
and this
one
which both have content and code close to what I want to do, but in the first one:
app.use(express.cookieParser());
while I don't want to use cookie at all, and the other one also has in code:
cookie: {
secure: process.env.ENVIRONMENT !== 'development' && process.env.ENVIRONMENT !== 'test',maxAge: 2419200000}...
Also, I found this on
github
which suggests for the client side (unity):
var socket = io.connect('http://localhost:9000');
socket.on('connect', function (socket) {
socket.on('authenticated', function () {
//do other things
})
.emit('authenticate', {token: jwt}); //send the jwt
});
meaning that:
1. socket is created
2. authentication is requested
but I think that the approach I found in the other article is better, where the socket is not created at all if the JWT for auth is not provided at the first ever connection request sent to "io", so if I'd do it I'd issue:
var socket = io.connect('http://localhost:9000', {query: {"JWT":"myjwt"}});
and in my server side where I have:
io.on("connection", function(socket){...});
I'd like to first get the JWT:
var jwt = socket.handshake.query["JWT"];
and then if auth will be unsuccessful, simply return socket.disconnect('reason') and do not open any connection at all (here maybe I just didn't understand, say, that the approach the Author took in the github source is using a middle ware technique and it is maybe also done before anything else).
I still could not find out what is the best practice that Gurus use, please help me get clear.

CakePHP Authentication on REST API

So I'm creating a REST API for a web app I'm developing, and I know the basic ways for authentication are either sending the credentials on each request or sending a token.
Since I have never used token before, I think I may send the credentials for each request. The point is I can't find any examples on how to handle this in the controller. Would it be something like this?
public function api_index() {
if(!$this->Auth->login()) return;
$this->set(array(
'models' => $this->Model->find('all'),
'_serialize' => array('models')
));
}
I don't really think this is the way AuthComponent::login() works, can I get some directions here please?
Alright, first a clarification about how AuthComponent::login works. In Cake 2.x that method does not do any authentication, but rather creates the Auth.User array in your session. You need to implement the actual authentication yourself (the User model is a natural place to do this). A basic authentication method might look like this:
App::uses('AuthComponent', 'Controller/Component');
public function authenticate($data) {
$user = $this->find('first', array(
'conditions' => array('User.login' => $data['login']),
));
if($user['User']['password'] !== AuthComponent::password($data['password']) {
return false;
}
unset($user['User']['password']); // don't forget this part
return $user;
// the reason I return the user is so I can pass it to Authcomponent::login if desired
}
Now you can use this from any controller as long as the User model is loaded. You may be aware that you can load it by calling Controller::loadModel('User').
If you want to authenticate every request, then you should then put in the beforeFilter method of AppController:
public function beforeFilter() {
$this->loadModel('User');
if(!$this->User->authenticate($this->request->data)) {
throw new UnauthorizedException(__('You don\'t belong here.'));
}
}
All of the above assumes that you pass POST values for login and password every time. I think token authentication is definitely the better way to go, but for getting up and running this should work. Some downsides include sending password in cleartext (unless you require ssl) every request and the probably high cpu usage of the hashing algorithm each time. Nevertheless, I hope this gives you a better idea of how to do authentication with cakephp.
Let me know if something needs clarifying.
Update:
Since posting this, I found out that you can actually use AuthComponent::login with no parameters, but I am not a fan of doing so. From the CakePHP documentation:
In 2.x $this->Auth->login($this->request->data) will log the user in with
whatever data is posted, whereas in 1.3 $this->Auth->login($this->data)
would try to identify the user first and only log in when successful.
AuthComponent::login() creates a session variable that stores the user data, so say you had data that was something like.
$data = array('User' => array('id' => 1, 'username' => 'johndoe'));
Then you would use
$this->Auth->login($data);
And access the data with
$this->Auth->user('User');
To get the user id
$this->Auth->user('User.id');
In your AppControllers beforefilter put $this->Auth->deny(); that will deny all actions to someone who is not logged in. Then in each controllers before filter you want to $this->Auth->allow(array('view')); 'view' being the name of an action you want to be public.
http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html
Cakephp 2.X, After lots of research about this on internet I did not find any satisfactory answer So I found a way myself to do this. May be this answer will help some folks in future. This answer is only applicable for REST API's in cake php.
Add following line in your logic action of REST API before checking for the $this->Auth->login().
$this->request->data['User'] = $this->request->data ;
if($this->Auth->login()){
echo "Hurray You are logged In from REST API.";
}
else
{
throw new UnauthorizedException(__('You don\'t belong here.'));
}
Desciption : As #threeve said in one of the answers that $this->Auth->login() does not do any authentication itself. but rather creates the Auth.User array in your session. For Authentication we require our $this->request->data to be inside User array since User is the model which will check the Credentials in Database. Therefore we have to pass the data to Auth as it is required when not using REST API's. Rest of the things will be handled by Cake and Auth itself.
Any enhancements or suggestions on this answer are most welcome.