Lomkit / laravel-rest-api

Generate Api in seconds
https://laravel-rest-api.lomkit.com/
MIT License
438 stars 23 forks source link

OAuth2 redirect implementation #135

Open cay89 opened 1 month ago

cay89 commented 1 month ago

Description

I want to use OpenID Connect's authorization code flow here:

image

Part of this flow involves redirecting to the client after authentication is completed on the Identity Provider's side. However, it seems that this part of the flow is not yet implemented in Laravel Rest API.

By default, the Swagger UI sets the redirect URI to /oauth2-redirect.html, and there is an implementation to handle the response: https://github.com/swagger-api/swagger-ui/blob/master/dist/oauth2-redirect.html. The default redirect URI also can be change as well: https://stackoverflow.com/questions/49518868/oauth2-authorization-in-nelmioapidocbundle/49519134#49519134.

So maybe the easiest way to handle this is to install Swagger UI in my own project and then create a route where I load Swagger UI's oauth2-redirect.html. If I want to change the redirect URI as well, then I want to override your blade template where the Swagger UI was loaded. Later it might be a problem that the Swagger UI version used by the Laravel Rest API is different from mine, so I have to pay attention to that too.

The question is, do I have to do this with a workaround, or do you plan for the Laravel Rest API to support this as well?

GautierDele commented 3 weeks ago

I'm not quite sure to understand the problem you have there, using openID, you have the "openIdConnectUrl" and then you'll need to redirect to the correct url I'm not an openApi expert but this seems to be possible using this: https://github.com/swagger-api/swagger-ui/blob/master/dist/oauth2-redirect.html#L12

For the security schemes I used the openApi examples, this might not be related to SwaggerUI

If I understand well, the problem is only on the redirect side once openId protocol has been approved on server ?

cay89 commented 3 weeks ago

@GautierDele The problem occurs when the Identity Provider redirects you back to the application (in our case, the API documentation). A 404 error will occur if we do not set the oauth2RedirectUrl in the SwaggerUI settings, or create a route for the default /oauth2-redirect.html URL which does not exist by default. The Swagger UI code above should be served at this URL to handle the response and 'remember' the tokens for use in the documentation.

Here's my rough workaround for testing routes/api-php:

Route::get('/oauth2-redirect.html', function () {
    return OAUTH2_REDIRECT;
})->name('oauth2-redirect');

// https://github.com/swagger-api/swagger-ui/blob/master/dist/oauth2-redirect.html
const OAUTH2_REDIRECT = <<<CODE
<!doctype html>
<html lang="en-US">
<head>
    <title>Swagger UI: OAuth2 Redirect</title>
</head>
<body>
<script>
    'use strict';
    function run () {
        var oauth2 = window.opener.swaggerUIRedirectOauth2;
        var sentState = oauth2.state;
        var redirectUrl = oauth2.redirectUrl;
        var isValid, qp, arr;

        if (/code|token|error/.test(window.location.hash)) {
            qp = window.location.hash.substring(1).replace('?', '&');
        } else {
            qp = location.search.substring(1);
        }

        arr = qp.split("&");
        arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
        qp = qp ? JSON.parse('{' + arr.join() + '}',
                function (key, value) {
                    return key === "" ? value : decodeURIComponent(value);
                }
        ) : {};

        isValid = qp.state === sentState;

        if ((
          oauth2.auth.schema.get("flow") === "accessCode" ||
          oauth2.auth.schema.get("flow") === "authorizationCode" ||
          oauth2.auth.schema.get("flow") === "authorization_code"
        ) && !oauth2.auth.code) {
            if (!isValid) {
                oauth2.errCb({
                    authId: oauth2.auth.name,
                    source: "auth",
                    level: "warning",
                    message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
                });
            }

            if (qp.code) {
                delete oauth2.state;
                oauth2.auth.code = qp.code;
                oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
            } else {
                let oauthErrorMsg;
                if (qp.error) {
                    oauthErrorMsg = "["+qp.error+"]: " +
                        (qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
                        (qp.error_uri ? "More info: "+qp.error_uri : "");
                }

                oauth2.errCb({
                    authId: oauth2.auth.name,
                    source: "auth",
                    level: "error",
                    message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
                });
            }
        } else {
            oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
        }
        window.close();
    }

    if (document.readyState !== 'loading') {
        run();
    } else {
        document.addEventListener('DOMContentLoaded', function () {
            run();
        });
    }
</script>
</body>
</html>
CODE;