panva / openid-client

OAuth 2 / OpenID Connect Client API for JavaScript Runtimes
MIT License
1.83k stars 392 forks source link

401 response from token endpoint #247

Closed EmDee closed 4 years ago

EmDee commented 4 years ago

Problem

I am using Gitlab.com as oAuth provider. My setup has worked in the past, but in the past month or two I keep getting the following error from the openid-client:

Error OPError: invalid_request (The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.)
    at processResponse (/Users/foo/node_modules/openid-client/lib/helpers/process_response.js:45:13)
    at Client.grant (/Users/foo/node_modules/openid-client/lib/client.js:1235:26)
    at processTicksAndRejections (internal/process/task_queues.js:97:5) {
  error: 'invalid_request',
  error_description: 'The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.'
}

I traced the error back to a 401 response from the token endpoint. However, I'm unsure about the error description which states that I might be missing or sending wrong parameters.

I want to verify that I'm using the client library correctly, before I escalate the issue towards GitLab.

This is my implementation (stripped to the bare minimum):

const express = require('express');
const app = express();
const session = require('express-session');

// cookieSession config
app.use(session({
    secret: 'keyboard cat',
    resave: false,
    saveUninitialized: true,
    cookie: { secure: false, sameSite: "lax" }
}));

const { generators } = require('openid-client');
const { Issuer } = require('openid-client');

Issuer.discover('https://gitlab.com').then(function(gitlabIssuer) {
    // console.log('Discovered issuer %s %O', gitlabIssuer.issuer, gitlabIssuer.metadata);

    const client = new gitlabIssuer.Client({
        client_id: "someid",
        client_secret: "somesecret",
        redirect_uris: ["http://localhost:3000/auth/gitlab/callback"],
        response_types: ['code'],
    });

    /* Auth middleware */
    app.use(function(req, res, next) {
        /* Do not intercept auth urls */
        if (req._parsedOriginalUrl != null && req._parsedOriginalUrl.pathname == '/auth/gitlab/callback') {
            console.log("Got oAuth callback")
            next()
            return
        }

        /* Check if the user is logged in */
        if (req.session.tokenSet) {
            console.log("Has token set")
        } else {
            console.log('User not logged in')

            const code_verifier = generators.codeVerifier()
            const code_challenge = generators.codeChallenge(code_verifier);

            req.session.authRequest = {
                code_verifier,
                code_challenge
            };

            const authUrl = client.authorizationUrl({
                scope: 'openid api',
                code_challenge,
                code_challenge_method: 'S256'
            });

            // Store in session
            req.session.save()
            res.redirect(authUrl)
        }
    })

    app.get('/auth/gitlab/callback', (req, res) => {
        console.log("Authenticate user")

        const params = client.callbackParams(req);

        console.log("Params:", params)

        const code_verifier = req.session.authRequest.code_verifier;

        console.log("code_verifier", code_verifier)
        client.oauthCallback("http://localhost:3000/auth/gitlab/callback", params, { code_verifier })
            .then(tokenSet => {
                req.session.authRequest = null

                console.log('received and validated tokens %j', tokenSet);

                req.session.tokenSet = tokenSet

                res.redirect("/")
            })
            .catch(error => {
                console.log("Error", error)
            });
    })
});

module.exports = app;

And the complete output I'm getting:

User not logged in
Got oAuth callback
Authenticate user
Params: {
  code: 'd70ebcab2937d0876a302bc23a2e87d4be12d865e907da1d631d603fac3a233d'
}
code_verifier bFWBhrWz3LXlU0z1NvGbvhKoF3Q8s4JnAc9IumukKl8
Error OPError: invalid_request (The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.)
    at processResponse (/Users/foo/node_modules/openid-client/lib/helpers/process_response.js:45:13)
    at Client.grant (/Users/foo/node_modules/openid-client/lib/client.js:1235:26)
    at processTicksAndRejections (internal/process/task_queues.js:97:5) {
  error: 'invalid_request',
  error_description: 'The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.'
}

I'm using the latest version of openid-client, i.e. 3.14.1.

The implicit flow works btw. Any obvious mistakes here?

panva commented 4 years ago

You could improve your setup by using a random state for every request, PKCE also handles CSRF protection for you but i can't see at first glance that gitlab actually supports pkce. So please, use a random state parameter.

Otherwise you're good and you should poke gitlab for an explanation of the error, the client is doing a conform request.

EmDee commented 4 years ago

Thanks for the help.

Here is the related GitLab issue as a reference: https://gitlab.com/gitlab-org/gitlab/-/issues/213643

panva commented 4 years ago

A stupid guess, if they for whatever reason started forcing client_secret_basic auth in january - try setting your client's token_endpoint_auth_method to client_secret_basic.

The client uses the standard client_secret_basic by default.

panva commented 4 years ago

Also, they mention includes an unsupported parameter value and they don't seem to support PKCE (from looking at their openid-configuration endpoint), in which case - try not using it and use a random state as i said before.

Needless to say, both behaviours are not conform. Every AS must support client_secret_basic and all token_endpoint implementations must ignore unrecognized parameters.