node-oauth / express-oauth-server

🔒 Complete, compliant and well tested module for implementing an OAuth2 Server/Provider with express in node.js
https://www.npmjs.com/package/@node-oauth/express-oauth-server
MIT License
29 stars 8 forks source link

getClient always leading to HTTP-401 #17

Open beegentoo opened 1 year ago

beegentoo commented 1 year ago

Hi all,

to be honest: I don't know if I am missing something from the big picture, if I do something wrong or if something is broken. Currently we have a home-brewed single-sign-on solution running which works fine but is not an industrial standard. That's why we plan moving to OAuth2. I am currently in a very early prototyping phase to find my way into OAuth2 and see what could how be done.

I started a small plain JS prototype project using express-oauth-server and followed the Documentation, the examples (also the additional example at https://github.com/14gasher/oauth-example as well as the original documentation at https://node-oauthoauth2-server.readthedocs.io/en/latest/model/spec.htm

Testing is done by a quickly setup Grafana-Docker-Container which has been configured to authorize via OAuth2.

[auth.generic_oauth]
enabled = true
name = OAuth
client_id = GrafanaDemo
client_secret = some_secret
scopes = user:email,read:org
auth_url = http://dev-vm:3001/login/oauth/authorize
token_url = http://dev-vm:3001/login/oauth/access_token
api_url = http://dev-vm:3001/user

Now when logging in my small prototype gets invoked. Specifically the model-function getClient(). That function is implemented with no real logic:

getClient : (clientId, clientSecret) => {
    console.log(`getClient(${clientId}, ${clientSecret})`);
    return new Promise((resolve, reject) => {
        let client = {
            id: clientId,
            clientId: clientId,
            clientSecret: clientSecret,
            grants: [
                "authorization_code",
                "refresh_token"
            ],
            redirectUris: [
                "http://dev-vm:3000/login/generic_oauth" // Grafana redirect
            ]
        };
        resolve(client);
    });
}

However, this renders an empty, dead page in the browser. Examining the call by using curl I get a HTTP-401 and no further redirect etc:

*   Trying 127.0.1.1:3001...
* Connected to dev-vm (127.0.1.1) port 3001 (#0)
> GET /login/oauth/authorize?client_id=GrafanaDemo&redirect_uri=http%3A%2F%2Fdev-vm%3A3000%2Flogin%2Fgeneric_oauth&response_type=code&scope=user%3Aemail+read%3Aorg&state=zUFDlXkJUTCSaeqipcRm9HlviQ_iZ9075WjlAIFZ2ws%3D HTTP/1.1
> Host: dev-vm:3001
> User-Agent: curl/7.81.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 401 Unauthorized
< X-Powered-By: Express
< www-authenticate: Bearer realm="Service"
< Date: Tue, 10 Oct 2023 08:24:14 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
< Content-Length: 0
< 
* Connection #0 to host dev-vm left intact

What am I doing wrong or am I missing something?

Additional Iinfo: I implemented

Additional Info 2 (oh dear, it's been a long time since I filed a bug report): Node: 18.17.1 Direct Dependencies:

jankapunkt commented 1 year ago

Hi @beegentoo thank you for reporting. From your info I assume you implement the Authorization Code Grant workflow, right? There are multiple stages of the workflow, potentially returning a 401 response. Would you please also make another curl request that prints the full error response?

beegentoo commented 1 year ago

Hi @jankapunkt thanks for your reply. To be honest, I am not really implementing a specific workflow (yet).

The plan is to get "a foot into the door" and get started at all. But from what I read from the intended test-client (i.e. Grafana's docs) it should be the authorization code grant.

Regarding the full curl response: I got what I posted above, nothing more (see the "content-length: 0" header in the response). I managed to get a JSON-Response with an error when providing an empty model with no functions defined.

binajmen commented 9 months ago

Hi @beegentoo, did you manage to setup a working example ? I'm facing the same issue as you were.

jankapunkt commented 9 months ago

@beegentoo can you please show your model implementation and how you use the middleware?

beegentoo commented 9 months ago

Hello @binajmen, Hello @jankapunkt

I stopped trying a few days afterwards with no success since other projects got more urgent.

I can not offer the full model implementation I had when I filed this bug since I did a few attempts to get up and running. But it should have been something like this (Github won't let me attach the file itself):

var bodyParser = require("body-parser");
var express = require("express");
var OAuthServer = require("@node-oauth/express-oauth-server");

var app = express();
app.oauth = new OAuthServer({
    model: {
        getClient : (clientId, clientSecret) => {
            console.log(`getClient(${clientId}, ${clientSecret})`);
            return new Promise((resolve, reject) => {
                /*
                let client = {
                    id: clientId,
                    clientId: clientId,
                    clientSecret: clientSecret,
                    grants: [
                        "authorization_code",
                        "refresh_token",
                        "client_credentials"
                    ],
                    redirectUris: [
                        "http://bjudas-dev-vm:3000/login/generic_oauth"
                    ]
                };
                */
                let client = {
                    clientId: clientId,
                    clientSecret: clientSecret
                };
                resolve(client);
            });
        },
        saveAuthorizationCode : (code, client, user) => {
            console.log(`saveAuthorizationCode`);
            return code;
        },
        getAccessToken : (accessToken) => {
            console.log(`getAccessToken`);
            return {
                accessToken : accessToken,
                accessTokenExpiresAt: new Date().toString()
            }
        },
        getAuthorizationCode : (authorizationCode) => {
            console.log(`getAuthrorizationCode`);
            return new Promise((resolve, reject) => {
                let code = {
                    authorizationCode : authorizationCode
                }
                resolve(code);
            });
        },
        generateAuthorizationCode : (client, user, scope) => {
            console.log(`generateAuthorizationCode`);
            return new Promise((resolve, reject) => {
                resolve("0815");
            })
        },
        verifyScope : (token, scope) => {
            console.log(`verifyScope`);
        },
        getUser : (username, password) => {
            console.log('getUser');
        },
        getUserFromClient : (client) => {
            console.log(`getUserFromClient`);
        },
        generateAccessToken : (client, user, scope) => {
            console.log(`generateAccessToken`);
        }
    },
    grants: ['authorization_code', 'refresh_token', "client_credentials"],
    accessTokenLifetime: 60 * 60 * 24, // 24 hours, or 1 day
    allowEmptyState: true,
    allowExtendedTokenAttributes: true,
  });

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended:false}));
app.use(app.oauth.authorize());

app.use(function(req, res) {
    console.log("Foo");
    res.send("Secret Area");
});
app.listen(3001);

As you can see: this is a module plastered with Debug-Logging just to find out exactly where the problem occurs and where I did something wrong / a bug exists.