openid / AppAuth-JS

JavaScript client SDK for communicating with OAuth 2.0 and OpenID Connect providers.
Apache License 2.0
985 stars 161 forks source link

VueJS SPA. Question about the authorization listener. #98

Closed SterlingAr closed 5 years ago

SterlingAr commented 5 years ago

Expected Behaviour

The authorization_code should be intercepted by the AuthorizationNotifier.

Describe the problem

I am trying to implement an authorization_code flow within a web SPA, with vue.

When I load the app, I start the whole process of login and consent, I am then redirected back to the app. However, the authorization_code is in the URL, like this:

http://localhost:8080/dashboard?code=u4XrDgPK8ANvn...PJYqC7v0&scope=admin&state=GAomHd00kF

From the examples, I understand that the response should be intercepted by AppAuth, but I am actually leaving the app to a external login page, I cannot expect that behaviour to work, as far as I know, I am actually starting the whole auth process again when I'm redirected back to the app.

My question is, how Is the notifier supposed to work, when I'm reloading the page?

I cannot wrap my head around this, I'm sure I'm missing something obvious.

Environment

constructor() {
        this.notifier = new AuthorizationNotifier();
        this.authorizationHandler = new RedirectRequestHandler();

        this.notifier.setAuthorizationListener((request, response, error) => {
        // I am never reaching this step
            if (response) {
                this.code = response.code;
            }
        });

        this.authorizationHandler.setAuthorizationNotifier(this.notifier);
    }

    startAuthorizationFlow(authConfig) {
        this._fetchServiceConfiguration(authConfig.openIdProvider).then(() => {
            this._makeAuthorizationRequest(authConfig.clientConfig);
        });
    }

    _fetchServiceConfiguration(openIdProvider)
    {
      return AuthorizationServiceConfiguration.fetchFromIssuer(openIdProvider)
        .then(response => {
            this.configuration = response;
        })
    }

    _makeAuthorizationRequest(clientConfig)
    {
        let request = new AuthorizationRequest({
            client_id: clientConfig.clientId,
            redirect_uri: clientConfig.redirectUri,
            response_type: AuthorizationRequest.RESPONSE_TYPE_CODE,
            state: undefined,
            scope: '',
            extras: {
                'prompt': 'consent',
                'access_type': 'offline'
            }
        });

        this.authorizationHandler.performAuthorizationRequest(this.configuration, request);
    }
tikurahul commented 5 years ago

So AuthorizationHandler calls completeAuthorizationRequestIfPossible(). It's here (https://github.com/openid/AppAuth-JS/blob/master/src/authorization_request_handler.ts#L118).

This is what RedirectRequestHandler does (https://github.com/openid/AppAuth-JS/blob/master/src/redirect_based_handler.ts#L85).

I would check to see if this is true.

SterlingAr commented 5 years ago

I still don't understand how is that code reached if I am leaving the page? I open SPA, I am redirected to an external login page and then redirected back, whole process starts again. This is what I can't figure out.

tikurahul commented 5 years ago

It typically runs after a page load, where the AuthorizationRequestHandler dispatches a call to completeAuthorizationRequestIfPossible().

SterlingAr commented 5 years ago

I've researched into the code and found out that the parameter locationLike is set with only the origin value while performing completeAuthorizationRequestIfPossible().

It does work if I put a hash in front of the path, like this:

http://localhost:8080/#code=YjlrlAZS2w...Rps&scope=admin&state=i2rN9V7AcS

I found it was because the query was parsed using the hashParameter set to true:

let queryParams = this.utils.parse(this.locationLike, true /* use hash */);

However, the OP I use returns the code as such: http://localhost:8080/?code=ebRGC8vyDrRyaYRhR8TYjXLmKikvvEvuQTl6AYI1h-o.pZIpwegOa8R9wYxMM0CTtxG2LN-qLcjLwzK_U5729fE&scope=admin&state=gXWj4nlB

I don't understand this piece of code, should my redirect_uri have a hash to work?

My OP doesn't allow that for this type of flow (authorization_grant + pkce).

PD: sorry for the delay in answering, holidays and such.

tikurahul commented 5 years ago

So you can use your own query string parser. In the past when I have encountered situations like this, this is what I did:

class NoHashQueryStringUtils extends BasicQueryStringUtils {
  parse(input: LocationLike, useHash?: boolean): StringMap {
    return super.parse(input, false /* never use hash */);
  }
}

I can now construct an instance of RedirectRequestHandler with an instance of NoHashQueryStringUtils. Something along the lines of:

this.authorizationHandler = new RedirectRequestHandler(
  new LocalStorageBackend(),
  new NoHashQueryStringUtils(), 
  window.location,
  new DefaultCrypto()); // null if your provider does not support PKCE
SterlingAr commented 5 years ago

I have tried that and it worked perfectly, wonderful library, exactly what I needed.

Thank you for the explanation.