Angular-oauth2-oidc: Failed to load openid-configuration. No 'Access-Control-Allow-Origin' header is present

Created on 31 Jul 2018  路  14Comments  路  Source: manfredsteyer/angular-oauth2-oidc

Hi,

my Identity Provider does not allow to load a discovery document.
2018-07-31 14_53_50-angularngrxmaterialstarter

I am going to use your samples regarding configuration without discovery:

export class AppComponent {
    constructor(private router: Router, private oauthService: OAuthService) {
        this.configureWithoutDiscovery();
    }

    private configureWithoutDiscovery() {
        this.oauthService.configure(noDiscoveryAuthConfig);
        this.oauthService.tokenValidationHandler = new NullValidationHandler();
        this.oauthService.tryLogin();
   }

But when i execute the code above absolutely nothing happens in the console.
No consent screen, no error. nothing.

When i use this.oauthService.loadDiscoveryDocumentAndLogin(); it works nice on localhost but when deploying to production i am told the error with No 'Access-Control-Allow-Origin' ...

My noDiscoveryAuthConfig document is fully configured. (skipping the JWKS section)

export const noDiscoveryAuthConfig: AuthConfig = {
    clientId: 'aistoxt_ig_client',
    redirectUri: environment.pingIdentity.callbackURL,
    postLogoutRedirectUri: '',
    loginUrl: environment.pingIdentity.loginURL,
    scope: 'openid profile email',
    resource: '',
    rngUrl: '',
    oidc: true,
    requestAccessToken: true,
    options: null,
    issuer: environment.pingIdentity.issuer,
    clearHashAfterLogin: true,
    tokenEndpoint: environment.pingIdentity.tokenEndpointURL,
    userinfoEndpoint: environment.pingIdentity.userinfoEndpointURL,
    responseType: 'token',
    showDebugInformation: true,
    silentRefreshRedirectUri: environment.pingIdentity.silentRefreshRedirectUri,
    silentRefreshMessagePrefix: '',
    silentRefreshShowIFrame: false,
    silentRefreshTimeout: 20000,
    dummyClientSecret: null,
    requireHttps: 'remoteOnly',
    strictDiscoveryDocumentValidation: false,
    customQueryParams: {
        pfidpadapterid: 'OAuthAdapterCCDS',
    },
    silentRefreshIFrameName: 'angular-oauth-oidc-silent-refresh-iframe',
    timeoutFactor: 0.75,
    sessionCheckIntervall: 3000,
    sessionCheckIFrameName: 'angular-oauth-oidc-check-session-iframe',
    disableAtHashCheck: false,
    skipSubjectCheck: false,
};

Any clue?

Most helpful comment

@jeroenheijmans thanks for pointing out the need CORS.
for sure this will help me giving it another try in my "people problem" :-) (you nailed it)

BTW: updated my last comment... Never wanted to be mad at you guys. Love the work you do. Really appreciate it!

All 14 comments

Make sure your identity provider server is up and running!
I faced that issue when, for some reason, the app pool was stopped. It was the same error.

Identity Server is working. We are using PingFederate.

I see, then you must check PingFederate settings related to CORS:
https://docs.pingidentity.com/bundle/pa_sm_ApplyPolicies_pa41/page/pa_t_Cross-Origin_Request_Rule.html

Sounds good. i will give this a try.

seems like my IdentityProvider guys do not put a CORS setting for my URL. Of course they didn't give me a explanation why.

But i would like to come back to my question:
Is there a possibility to give this library manually the discovery document in order NOT to have the need for loading the discovery document?

@enterprisebug As far as I know there is no way to give the discovery document to the library as a file, apart from loading it the way you already mentioned.

@enterprisebug Looking a bit more into the code, it might be somewhat possible. Since the loadDiscoveryDocumentAndTryLogin and loadDiscoveryDocumentAndLogin methods are really just convenience methods that chain multiple public methods together, you should be able to do this yourself.

And then it turns out that loadDiscoveryDocument allows you to provide an URL as a parameter (the convenience overloads linked above don't do so). So something like this might be worth trying:

this.oauthService
    .loadDiscoveryDocument('/local/path/to/file/for/openid-configuration')
    .then(doc => this.oauthService.tryLogin(myOptions)); // etc., however you did that before

To clarify, this allows you to download the disco document and serve it on some local path (avoiding CORS issues) or on your API where CORS is enabled properly for your SPA's domain.

I haven't tested this but I think it should work. Let us know if you've tried it and whether it helped.

@jeroenheijmans sounds very promising i will give this a try. Thanks for your hint!

@jeroenheijmans seems i found a solution based on your idea.

I think i will do a contribution in the docs for this issue.
It is currently not fully solved but partly. I will describe it shortly.

private configureWithoutDiscovery() {
  this.oauthService.configure(noDiscoveryAuthConfig);
  this.oauthService.tryLogin();
  if (!this.oauthService.hasValidIdToken() || !this.oauthService.hasValidAccessToken()) {
    this.oauthService.tokenValidationHandler = new NullValidationHandler();
    // local discovery document located at: http://localhost:4200/assets/openid-configuration.json
    this.oauthService.loadDiscoveryDocument(env.pingIdentity.discoveryDocumentURL)
      .then(doc => this.oauthService.initImplicitFlow());
  } else {
    // redirect to a page that needs no authentication
    this.router.navigate(['/about']);
  }
}

This works but without JWKS. Because the angular-oauth2-oidc-lib want to load the document located at jwks_uri which does a CORS request as well. I have replaced the jwks_uri with a local document as well but the the problem is that the issuer cannot be validated...
That is why i commented out jwks_uri in the custom openid-configuration.json to come over the CORS requests. In combination with the NullValidationHandler it works. (Of course without token validation)

Regarding your code sample, specifically the overall structure, I'd recommend placing everything below tryLogin() in a then(...) handler. Your code might now work because currently tryLogin() usually runs synchronously, but the signature suggests it's possibly _asynchronous_.

Regarding the original problem and needed workaround, I think the root cause is that your ID Server doesn't allow CORS for your Angular application. I appreciate that sometimes it's hard to change something like that (e.g. if the release cycle and/or codebase are outside of your control), but it really makes no sense if the ID Server supports SPA clients through implicit flow but has no way to enable CORS for them.

@jeroenheijmans what i am missing is a argumentation why CORS should be enabled on the ID Server.
May you have some arguments for me to convince our IT guys.

The discovery document is placed in a folder named "well-known" for a reason: it's something that _should_ probably be publicly accessible. An Identity Server that is used to serve javascript applications should enable CORS for those public endpoints and documents.

Or, you could quote MDN:

Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional HTTP headers to tell a browser to let a web application running at one origin (domain) have permission to access selected resources from a server at a different origin.

So CORS _is_ the exact mechanism to handle this. The IdServer should really allow at least the disco document to be retrieved from other domains that serve your applications.

For this reason, e.g. the IdentityServer4 documentation for JavaScript apps has a step to enable CORS.

SAAS solutions for Identity like Auth0 will also allow CORS from * for the disco document.

If all that doesn't convince them, then I think you don't have a "tech problem" but a "people problem", requiring a people-solution :smile:

PS. Care with the caps ("WHY") and your choice of words ("bloody workaround"). It comes across as if you're mad at us, and we are just some internet strangers trying to be helpful. :wink:


Edit: as a footnote, I was triggered to have a peek at the docs for PingFederate which you mentioned being the IDServer. As far as I can tell their own documentation on CORS also suggests that administrators should add javascript application's domains as trusted origins for (e.g.) the well-known configuration endpoint, amongst others. Maybe that helps too?

@jeroenheijmans thanks for pointing out the need CORS.
for sure this will help me giving it another try in my "people problem" :-) (you nailed it)

BTW: updated my last comment... Never wanted to be mad at you guys. Love the work you do. Really appreciate it!

@jeroenheijmans the arguments you mentioned seems to have been enough! :-)

Found out that our IT guys have CORS setting for https://*.mycompany.com. If my SPA would be hosted there it would have worked.
BUT: My website is hosted under https://*.mycompany.azurewebsites.net. We did not have a CORS setting for this. The CORS rule will be applied - simple in the end

This means we can close this issue.
The library will do it's job.

Solution: CORS rule has to be set in IdServer

i learned a lot on this one: i have focused on the workaround instead of the root cause. @jeroenheijmans you are the man! thanks for pointing out the right direction.

cc: @MarcDrexler

Was this page helpful?
0 / 5 - 0 ratings

Related issues

zulander1 picture zulander1  路  4Comments

jeroenheijmans picture jeroenheijmans  路  3Comments

uzzafar picture uzzafar  路  4Comments

prmces picture prmces  路  4Comments

jeroenheijmans picture jeroenheijmans  路  4Comments