Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authentication #411

Closed
aaronbartell opened this issue Aug 22, 2017 · 39 comments
Closed

Authentication #411

aaronbartell opened this issue Aug 22, 2017 · 39 comments
Labels
question user / developer questions

Comments

@aaronbartell
Copy link

It would be great if there were mechanisms to automate authentication of the browser-based Theia so it can be accessed in a secured fashion via API.

My scenario for using Theia is in a bigger web application that has a need for a file explorer, code editor, and terminal. In my web application a user will click a button to open Theia to their specific container. At that point I will check to see if Theia is running and if not I will start it and include a passphrase that my web application will use to make it so only the current user can gain access to this particular web URL.

Side Note: HTTP Basic Auth is no longer a good approach because Chrome blocks it when you try to put them credentials on the URL (i.e. http://user:password@site.com:1234/path). Chrome, as of today, will issue an error when the page attempts to load resources (css, js, etc).

It seems JSON Webtokens are all the rage. I've not used them yet. The best approach is probably to allow for authentication extensions/plugins. Then Theia could ship with some popular default auth implementations but allow others to develop their own.

@hexa00
Copy link

hexa00 commented Aug 22, 2017

We wanted to leave this out of Theia and for example use this technique: https://github.com/auth0/nginx-jwt

Leaving the auth part to the webserver / reverse proxy.

Does that look like a possible path to you ?

@aaronbartell
Copy link
Author

In my scenario I will have a single machine with many of instances of Theia Node.js server running. Each Theia server could belong to a different user and would need different credentials.

I briefly read through nginx-jwt and I believe I would need a separate Nginx server for each instance of Theia for my scenario. I'd prefer to not add Nginx to the stack of each container.

If you would like to keep authentication out of Theia I am wondering if we could at least document how somebody could add it manually (I am still new to the Theia code base and learning how it is structured).

For example, could authentication be added as Express middleware in the following location inside examples/browser/src-gen/backend/server.js?

function start(port, host) {
    const application = container.get(BackendApplication);
    application.use(express.static(path.join(__dirname, '../../lib'), {
        index: 'index.html'
    }));
    application.use(function(){   <----------------------
     // my auth strategy
    });
    return application.start(port, host);
}

@hexa00
Copy link

hexa00 commented Aug 23, 2017

We have not considered authentification much yet.
So anything is possible but we would like to keep it out of the Theia code as much as possible.

Your idea sounds possible it would need to be investigated more. We're open to proposals...

Note also that I used nginx-jwt only as an example, I thought for a production deployment one would use another server than express anyway in a reverse proxy configuration so any webserver would do.

If I may why would you need more than one instance of nginx ?

@aaronbartell
Copy link
Author

If I may why would you need more than one instance of nginx ?

I will have hundreds of users on a single instance of the operating system, each operating in their own container(n1). Each container has a browser-based IDE/terminal that the user can navigate to from their dashboard. When they click on the dashboard link I start up the IDE/terminal and only allow the user in if they have the correct HTTP Basic Authentication credentials. Each container needs to have separate credentials so other devs on the same machine can't access another's IDE/terminal.

n1 - using chroot, don't laugh :-)

I could have a reverse proxy server do the authentication, but that's the only function it would serve so I am wanting to have an extension in Theia to accomplish the task.

Your idea sounds possible it would need to be investigated more. We're open to proposals...

I like your focus on extensibility and am thinking extensions could also be allowed during server startup, though IoC via inversify might not be the approach you want because you don't want to have an authentication IoC interface. But what if there was a directory where Express middleware was stored and subsequently loaded at startup, and if someone desired they could include their own authentication middleware.

This SO post shows how Express routes stored in multiple files can be recursively loaded. The same could be done for Express middleware.

I propose to have a /middleware/startup directory where custom .js files could be placed.

Thoughts?

@svenefftinge
Copy link
Contributor

Could you explain why IoC should not be used here?

@aaronbartell
Copy link
Author

Could you explain why IoC should not be used here?

I probably shouldn't have opened that can of worms given my limited understanding of Theia's implementation of IoC.

My perspective (which may be incorrect): To have IoC you usually need an interface that declares what an eventual implementation of an interface (OO interface) needs to look like. For example, Theia currently has the Clipboard Service. My assumption is that if Theia used IoC (inversify) to implement allowance of authentication then Theia would inherently have authentication as a feature, which is what @hexa00 mentioned you don't want.

So I was trying to come up with ideas for implementing authentication that didn't require Theia to declare support for authentication. Maybe that can be done with IoC, I don't know. My mind sent me down the route of Express middleware, which could be generically/dynamically loaded by looking at a specific directory.

More... I am now seeing that the usage of Websockets in Theia will require authentication to go beyond the implementation of Express middleware because the Websocket communication would also need to be authenticated (I believe). So auth for Theia just become more complicated than I had originally envisioned. Sorry for not thinking through it more fully before posting.

@svenefftinge
Copy link
Contributor

That's fine, we can think and explore together. I just wanted to understand your concerns regarding IoC.
In fact we have something called a BackendApplicationContribution that gives you a callback to configure express. Wouldn't that be sufficient?

@aaronbartell
Copy link
Author

aaronbartell commented Aug 23, 2017

In fact we have something called a BackendApplicationContribution that gives you a callback to configure express. Wouldn't that be sufficient?

Could you confirm whether my understanding of BackendApplicationContributions is correct... when Theia starts it will iterate through the ./packages directory to load extensions**; for example, it will load ./packages/terminal. If that's correct then the challenge I have is inserting Websocket authentication into the existing terminal communication; same with the editor, though I can't seem to locate its Websocket code unless it is using connection.ts.

**I am not sure if "extension" and "Contribution" and "packages" are synonyms.

Side Note: I am fairly new to TypeScript so my perusing of the code is doubly complicated. Thanks for your patience.

@hexa00
Copy link

hexa00 commented Aug 23, 2017

A contribution is one implementation of an interface that is to be called in a certain context.
Multiple contributions for a particular context can be called such that all registred contributions are called for a specific contribution context.

For example: BackendApplicationContribution is defined in backend-applications.ts.

On creation of the BackendApplication (on app startup) all the BackendApplicationContributions will be called with the configure?(app: express.Application): void; method. Thus giving any BackendApplicationContribution a chance to configure the Express server.

Also once BackendApplication.start() is called (at the start of the main application) it will call the onStart?(server: http.Server): void; method of each BackendApplicationContribution. So that BackendApplications can start a websocket server.

So:
extension: Can mean a Theia package as in what is in packages or possibly an implementation of a IoC interface.
contribution: is what I just explained
packages: refer to what is in packages/* since these are to be published on their own as an npm package.

Note that I'm not sure how the auth would work exactly with websocket etc but I think you could configure express such that the route services/* under wich all services are accessed could be setup such that it needs auth.

And such a config could be done by adding a BackendApplicationContribution and implementing the configure method.

You can look at the terminal-backend-contribution.ts for an example of a BackendApplicationContribution that uses configure and start

@hexa00
Copy link

hexa00 commented Aug 23, 2017

Note however that an extension could use a path other than services...
Maybe we would need a registry of such routes

@aaronbartell
Copy link
Author

You can look at the terminal-backend-contribution.ts for an example of a BackendApplicationContribution that uses configure and start

Great explanation (one for the wiki) and thanks for giving me a place to start.

@jopit
Copy link
Contributor

jopit commented May 8, 2018

Given the changes for #1771, is creating a BackendApplicationContribution still the best way to implement authentication for Theia's websockets?

@akosyakov akosyakov mentioned this issue Jul 16, 2018
@ilstarno
Copy link

ilstarno commented Jan 8, 2019

**this is the simplest solution which asks for user authentication ..

add this on server.js**

function start(port, host, argv) {
if (argv === undefined) {
argv = process.argv;
}

const cliManager = container.get(CliManager);
return cliManager.initializeCli(argv).then(function () {
    const application = container.get(BackendApplication);
    application.use((req, res, next) => {

// -----------------------------------------------------------------------
// authentication middleware

const auth = {login: 'username_here', password: 'password_here'} // change this

// parse login and password from headers
const b64auth = (req.headers.authorization || '').split(' ')[1] || ''
const [login, password] = new Buffer(b64auth, 'base64').toString().split(':')

// Verify login and password are set and correct
if (!login || !password || login !== auth.login || password !== auth.password) {
res.set('WWW-Authenticate', 'Basic realm="401"') // change this
res.status(401).send('Authentication required.') // custom message
return
}

// -----------------------------------------------------------------------
// Access granted...
next()

});
application.use(express.static(path.join(__dirname, '../../lib'), {
index: 'index.html'
}));
return application.start(port, host);
});
}

@rhildred
Copy link

I did this @ilstarno. Ended up shooting myself in the foot because I used a Docker volume to substitute in my own server.js. When the generated server.js changed I had a debugging problem that I failed for a long time at because I didn't remember that I had messed with it.

@hexa00 I don't see how to register a BackendApplicationContribution. Is it as simple as adding my own module to the package.json?

@ilstarno
Copy link

@rhildred, first try to build the image normally, later on try to access at your docker container and inside the server.js file replace the start function with this
`function start(port, host, argv) {
if (argv === undefined) {
argv = process.argv;
}

const cliManager = container.get(CliManager);
return cliManager.initializeCli(argv).then(function () {
    const application = container.get(BackendApplication);
    application.use((req, res, next) => {

// -----------------------------------------------------------------------
// authentication middleware

const auth = {login: 'username_here', password: 'password_here'} // change this

// parse login and password from headers
const b64auth = (req.headers.authorization || '').split(' ')[1] || ''
const [login, password] = new Buffer(b64auth, 'base64').toString().split(':')

// Verify login and password are set and correct
if (!login || !password || login !== auth.login || password !== auth.password) {
res.set('WWW-Authenticate', 'Basic realm="401"') // change this
res.status(401).send('Authentication required.') // custom message
return
}

// -----------------------------------------------------------------------
// Access granted...
next()

});
application.use(express.static(path.join(__dirname, '../../lib'), {
index: 'index.html'
}));
return application.start(port, host);
});
}`
save the file and restart your container and you should be good to go..

@akosyakov
Copy link
Member

akosyakov commented Feb 28, 2019

@ilstarno There is BackendApplicationContribution.configure callback to install additional express handers instead of modifying generated code. One can create a basic authentication extension using it.

@rhildred
Copy link

@akosyakov sorry to be dense about this. Perhaps it is all inversify magic. Do I just add a npm module of my own in the package.json that implements a BackendApplicationContribution interface with the .configure callback defined? Is there an example that I can follow.

@rhildred
Copy link

@akosyakov I also need to integrate StreetSide software's code spell-checker vscode extension somehow. Can I follow the same pattern for that?

@rhildred
Copy link

I think that it finally dawned on me. I do need to write a node package since, "A Theia extension is a node package declaring theiaExtensions property in package.json:"

{
  "theiaExtensions": [{
      "backend": "lib/myExtension/node/myextension-backend-module",
    }]
}

Then in myextension-backend-module I need to have the following:


import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application';

@injectable()
export class AuthClass implements BackendApplicationContribution {

    configure(app: Application): void {
        app.use(....);
    }

}

Am I correct @akosyakov ?

@akosyakov
Copy link
Member

@rhildred there is documentation on developing Theia extensions: https://www.theia-ide.org/doc/Authoring_Extensions.html It should cover your questions.

@rhildred
Copy link

rhildred commented Mar 1, 2019

@akosyakov You are right. It did answer my questions. I did yo theia-extension in an empty folder named authorization. Then in authorization/package.json I added a backend:

  "theiaExtensions": [
    {
      "frontend": "lib/browser/authorization-frontend-module",
      "backend": "lib/node/authorization-backend-module"
    }
  ]

in authorization/src/node/authorization-backend-module.ts

/**
 * Generated using theia-extension-generator
 */

import { AuthorizationContribution } from './authorization-contribution';
import {
    BackendApplicationContribution
} from "@theia/core/lib/node/backend-application";

import { ContainerModule } from "inversify";

export default new ContainerModule(bind => {
    // add your contribution bindings here

    bind(BackendApplicationContribution).to(AuthorizationContribution);

});

in authorization/src/node/authorization-contribution.ts

import { injectable} from "inversify";
import { Application } from 'express';
import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application';

@injectable()
export class AuthorizationContribution implements BackendApplicationContribution {

    configure(app: Application): void {
        app.use("/test/ping/", (req, res) =>{
            res.end("ok");
        });
    }

}

It was relatively simple. Thanks for your help everyone.

@cell
Copy link

cell commented Apr 17, 2019

Hi. As the Issue is not closed I add my two cents. I think authentication should stay out of theia. As it's single user, I can't see why authentication would be needed.

For those wanting to have multiple instances of theia on the same host and have authentication, you'll need a reverse-proxy anyway otherwise each instance has to be mapped to a different port on the hosts.

Now the question is which reverse-proxy to use. Nginx is the default choice nowadays, I prefer traefik for containers: it's the container running with labels which configure traefik on the fly. Here is an example for a docker registry which needs authentication. It's in the same compose file but the registry could be started separately, like yet-an-other-theia for an other user.

@akosyakov akosyakov added the question user / developer questions label Oct 4, 2019
@akosyakov
Copy link
Member

Someone on Reddit gave a good answer how to secure access to Theia (any web app actually): https://www.reddit.com/r/selfhosted/comments/dclcv7/how_would_you_secure_access_to_a_web_ide_in_a_vps/f295nxw/

@ordinaryparksee
Copy link

ordinaryparksee commented Dec 28, 2019

I published theia-middleware package https://www.npmjs.com/package/theia-middleware
Referenced to @ilstarno post

@ordinaryparksee
Copy link

ordinaryparksee commented Jan 10, 2020

@akosyakov i made theia-middleware for authentication but its working only latest version doesn't work with next.
how can i adapt to next version? do you know any documentation about next version?

@ordinaryparksee
Copy link

ordinaryparksee commented Jan 10, 2020

I think this app.use is not called

@injectable()
export class TheiaMiddlewareContribution implements BackendApplicationContribution {

configure(app: Application): void {
    console.log("!!!DEBUG!!!")
    app.use((request, response, next) => {
        console.log("!!!DEBUG2!!!")

@rhildred
Copy link

I think that you are right. I had my authorization pattern working since my previous post (March 21, 2019). The next version no longer works in my environment either.

@akosyakov
Copy link
Member

It sounds strange, we did not change anything about backend application lifecycle. Could someone share a GitHub repo to reproduce from sources?

@ordinaryparksee
Copy link

@akosyakov
Copy link
Member

@ordinaryparksee Is there a way to export it to GitHub or GitLab. We are using Gitpod for development and it does not support bit butcket yet.

@ordinaryparksee
Copy link

@akosyakov okay cool here imported
https://github.com/ordinaryparksee/theia-middleware
thank you

@akosyakov
Copy link
Member

I've updated to latest next and cannot reproduce your issue:
Screen Shot 2020-01-14 at 13 43 14

a pr for your repo which i used: ordinaryparksee/theia-middleware#1

@ordinaryparksee
Copy link

ordinaryparksee commented Jan 14, 2020

Oh.. i see
then, maybe there is a conflict with some other extensions
i'll posting soon when i found it

@ordinaryparksee
Copy link

ordinaryparksee commented Jan 14, 2020

I misunderstood you updated package.json!
Thank you for check, and sorry

@osbre
Copy link

osbre commented Feb 12, 2020

Example of authorization with cookies:

https://github.com/brilliant-code/theia-cookie-auth

@akosyakov
Copy link
Member

@osbre Does it work for web sockets as well?

@osbre
Copy link

osbre commented Feb 14, 2020

@akosyakov,

Just tried to connect websockets with Theia and this plugin.

In Browser console:

conn = new WebSocket('ws://127.0.0.1:5000/services')
conn.send('{"kind":"open","id":0,"path":"/services/commands"}')

Response from Browser "Network" tab:

{"kind":"ready","id":0}

So, it looks likes auth doesn't work for websockets

@posix4e
Copy link

posix4e commented Sep 18, 2020

I disagree with this approach. We can't protect this at the external layer, the app must protect itself or it can't be launched on a shared cluster. Plus special attention of locking it's network connection out becomes important. Should we create a new issue for people who want the password built into the app itself?

@akosyakov
Copy link
Member

There is no app, Theia is only a framework. Which product do you use? Example applications are not for production use.

@eclipse-theia eclipse-theia locked and limited conversation to collaborators May 28, 2021

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
question user / developer questions
Projects
None yet
Development

No branches or pull requests