Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Getting a 403 on reauthorization attempts running 2 client applications concurrently with SSO #1457

Open
MSDev52 opened this issue Oct 24, 2024 · 9 comments
Assignees

Comments

@MSDev52
Copy link

MSDev52 commented Oct 24, 2024

Version 6.3.10

.NET6

I am receiving a '403 access denied' when attempting reauthorization SSO between the 2 client applications.

I have 2 client applications and 1 IDS6 instance that I have inherited. I'm using ComponentSpace to handle the SAML protocol with my IDP. Initial login to either application is functional, SAML token is received from IDP, my ID Token and Access Tokens are both populated correctly.

I have a 15-minute timeout constraint on the client apps so the user must click a button to reauthenticate every 15 minutes. Testing the applications by myself (1 user) I find that if I open App1 authentication is successful. If I, then open app2 login is also successful.

Now I am logged into both applications (app1 and app2 running simultaneously)

Following this all attempts to re-auth for app1 and app2 receive a 403 error (Request Blocked) and this occurs on my Callback https:***/SAML/External/Callback.

For this I have the following configuration (timeouts shortened to 1 minute for testing purposes):
IdentityServer Config.cs:

new List<Client>
{
    //The Client class models an OpenID Connect or OAuth 2.0 client.
    new Client
    {
        ClientId = "app1",
        ClientName = "app1 Production",
        ClientSecrets =
        {
            new Secret("SuperSecret1".Sha256())
        },

        AllowedGrantTypes = GrantTypes.Implicit,
        RequireConsent = false,
        AllowOfflineAccess = true,
        
        #region Token Lifetime
        UserSsoLifetime = 60,   //1m
        AccessTokenLifetime = 60,   // 1m                
        IdentityTokenLifetime = 60,   //1m
        #endregion
        AllowAccessTokensViaBrowser = true,
        AlwaysIncludeUserClaimsInIdToken = true,
        AlwaysSendClientClaims = true,
        RedirectUris = { "app1/signin-oidc" },
        PostLogoutRedirectUris = { "https://app1" },
        BackChannelLogoutUri = "https://*****/***/SAML/SAML/SingleLogout",
        FrontChannelLogoutUri = "https://*****/***/SAML/SAML/SingleLogout",

        AllowedScopes = {
             Duende.IdentityServer.IdentityServerConstants.StandardScopes.OpenId,
             Duende.IdentityServer.IdentityServerConstants.StandardScopes.Profile,
             "offline_access", "api2", "user.domain", "api1", "app1"
        }
    },
    new Client
    {
        ClientId = "app2",
        ClientName = "app2 Production",
        ClientSecrets =
        {
            new Secret("superSecret2".Sha256())
        },

        AllowedGrantTypes = GrantTypes.Implicit,
        RequireConsent = false,
        AllowOfflineAccess = true,

        #region Token Lifetime
        UserSsoLifetime = 60,    //1m
        AccessTokenLifetime = 60,    // 1m                
        IdentityTokenLifetime = 60,  //1m
        #endregion

        AllowAccessTokensViaBrowser = true,
        AlwaysIncludeUserClaimsInIdToken = true,
        AlwaysSendClientClaims = true,
        RedirectUris = { "https://****/***/***/signin-oidc" },
        PostLogoutRedirectUris = { "https://app2" },
        BackChannelLogoutUri = "https://*****/***/SAML/SAML/SingleLogout",
        FrontChannelLogoutUri = "https://*****/***/SAML/SAML/SingleLogout",
       
        AllowedScopes = {
             Duende.IdentityServer.IdentityServerConstants.StandardScopes.OpenId,
             Duende.IdentityServer.IdentityServerConstants.StandardScopes.Profile,
             "offline_access", "api1", "api2", "app2", "user.domain"
        }
    }
};

To Reproduce

Open 2 IDS6 client applications hosted behind a reverse proxy, attempt reauthorization, get 403.

Expected behavior

Expectation is both client applications will re-auth with the correct user.

I am happy to share code and paths via email for clarity.

Additional context
I am hosted behind a reverse proxy.
Depending on how I have my token/cookie timeouts set when testing with 2 users one of the users will receive the wrong ID Token which makes me jump out of my skin every time.
Alternate cookie/token timeout settings will also get me a 'the sequence contains no elements' when going from app1 to app2.
I consistently find my IDSV cookies blocked, and I have no idea if this is causing or contributing to the issues.
Image

I believe this is going to be a multi-part question but first I need to determine why I'm getting a 403 when trying to re-authenticate with 2 apps running concurrently.

Thanks.

@MSDev52
Copy link
Author

MSDev52 commented Oct 25, 2024

I find that if I remove the SSO and Token timeouts from my clients and just allow the defaults I get the wrong user ID Token on reauthentication if another user is logged in. Please help me understand how and why I could possibly end up with another user's ID Token. There are only 2 of testing and this happens consistently.

This is the workflow from dev tools when this occurs.
Image

So, it appears anytime I don't make a round trip to the IDP I get the other users ID Token and they get mine.

@MSDev52
Copy link
Author

MSDev52 commented Oct 25, 2024

I would also like to reference this issue from IDS4:
IdentityServer/IdentityServer4#4466

-Same issue I'm having with IDS6, no answer here as apparently it was resolved via email and then closed leaving others with the same unresolved issue.

@MSDev52
Copy link
Author

MSDev52 commented Oct 31, 2024

Are we just waiting for the lifetime to expire on IDS6 before responding or is it always a ghost town in here?

@josephdecock
Copy link
Member

Hi @MSDev52 - I'm really sorry that we haven't gotten back to you sooner!

Can you please clarify what exactly you mean by "reauthorization"? What protocol requests are being made? It seems like this is related to all of your issues, but it also perhaps includes a SAML endpoint. IdentityServer doesn't implement SAML itself. There are at least two third party plugins that do - SustainSys and RSK. If the issue ends up being related to SAML, we'll probably end up needing to involve the SAML implementor's support.

If your idsrv cookie is blocked, that is definitely going to cause issues with single sign on and sign out. Usually modern browsers will tell you why the cookie was blocked, so that's one of the first things to look at.

@josephdecock josephdecock self-assigned this Nov 3, 2024
@MSDev52
Copy link
Author

MSDev52 commented Nov 4, 2024

Hi Joe and thank you for responding.

First, the initial issue with a 503 ended up being that the cookie header was to large and I was able to resolve that via CloudFront configuration so now the remaining issue is the wrong Id Token being returned.

I am using IdentityServer as the Service Provider and Azure Active Directory as the IdP. My initial request is leveraging ComponentSpace to retrieve a SAML token from the IdP and this workflow is always successful when we hit the IdP first. What I am referring to as 'reauthorization' are all subsequent calls to IdentityServer following the initial round-trip to the IdP. So, in this example I have my client set as such:

new client
{
    ClientId = "myClient.Dev",
    ClientName = "My Client Dev",
    ClientSecrets =
    {
      new Secret("mySuperSecret".Sha256())
    },
    AllowedGrantTypes = GrantTypes.Implicit,
    RequireConsent = false,
    AllowOfflineAccess = true,
    AccessTokenLifetime = 3600,
    IdentityTokenLifetime = 300,
    AllowAccessTokensViaBrowser = true,
    AlwaysIncludeUserClaimsInIdToken = true,
    AlwaysSendClientClaims = true,
    RedirectUris = { "https://mydomain/myapp/signin-oidc" },
    BackChannelLogoutUri = "https://mydomain/myapp.SAML/SAML/SingleLogout",
    AllowedScopes = {
        Duende.IdentityServer.IdentityServerConstants.StandardScopes.OpenId,
        Duende.IdentityServer.IdentityServerConstans.StandardScopes.Profile,
        "myapp"
    }
}

With this configuration, when my IdentityTokenLifetime expires in 5 minutes I have to authorize again. In this event I do not return to the IdP. When this workflow is initiated this is what it looks like in dev tools:
Image

-So, here I assume the workflow is handed off to IDS acting as the SP which is expected.

Here is the state of the IDS cookies for the auth and signin-oidc calls above:

Authorize: (everything looks good to me here)
Image

Signin-oidc (here we find blocked cookies on idsrv & idsrv.session and the stated reason is 'The cookie was blocked because it's path was not an exact match for or a super directory of the request URL's path')
-I do not know if this is presenting an issue or not.
Image

So, to clarify, if I set my AccessTokenLifetime to match my IdentityTokenLifetime timeout I can force a return to the IdP to ensure the correct user is authenticated but this isn't desireable. If I set my AccessTokenLifetime timeout longer than when my Identity Token expires a round trip to the IdP is not initiated and I end up with the wrong IdentityToken (the IdentityToken received in my ProfileService belongs to the last user who logged in).

I can see this happening in my ProfileService.cs class so I suspect that something is missing or configured incorrectly but have no idea what that may be.

Again, thanks for jumping in here, I am truly frustrated with this. Apologies if I added to much in my description of the problem (or not enough. I am happy to provide any further information that you would find applicable.

Thank you so very much for your time.

@MSDev52
Copy link
Author

MSDev52 commented Nov 5, 2024

Some additional information...
I am finding that in my ProfileService class, the GetProfileDataAsync(ProfileDataRequestContext context) method has the wrong client claims sent in the 'context' parameter when my Id Token times out.

So, when the ID Token times out and my Access Token is not yet expired, what I am calling a 're-auth' (please correct me if this is the wrong terminology) occurs and in this workflow methods in my ExternalController are not called (Challenge & Callback) and this is expected. In this event my debugger is catching the GetProfileDataAsync method with the wrong client claims.

When this happens I find I have the correct user's claims in context.Subject.Identity.Claims
and the wrong user's claims in context.Client.Claims.

Please help me understand.

@MSDev52
Copy link
Author

MSDev52 commented Nov 6, 2024

I have a Duende license, I've attended 2 training sessions ran by Joe and I cannot seem to get any support with this product. How do I get support for IdentityServer? Is buying the $12k Enterprise Edition literally the only way to get support with any of this?

@AndersAbel
Copy link
Member

I understand that you are frustrated with the solution not working as you expect, but I have to give some backgrounds to set expectations on support. The GitHub issues support is a best-effort support that has no SLA and no guaranteed response times. The priority support which is available to Enterprise Edition customers has a 2 business day SLA. For the GitHub issue tracker, we usually respond within a week, but there's no guaranteed answer times.

Over to the actual problem here. I would like to start by pointing out that the scope of the support is to answer specific questions and provide suggestions for troubleshooting. We do however not have the capacity to review code, architectural level setup questions etc.

I will do my best to help you forward, but it might be that we will have to refer you to our commercial consulting services where we can work on the code and setup together.

The first thing I notice is that it looks like IdentityServer is mapped to run in a subdirectory (by a reverse proxy?). Doing so adds some additional complexity. It is also important to remember that while cookies can be limited to a specific path, that is more of performance optimization than a security boundary. Our recommendation is to deploy IdentityServer to it's own host name.

I am also surprised that the OpenIdConnect.Nonce cookie does not have a path qualificiation - it is normally restricted to the /signin-oidc path.

It is however correct that the idsrv and idsrv.session cookies are blocked in the request to /signin-oidc. The IdentityServer session cookies are not expected to be available for the client application. The protocol is designed for the assumption that these services are running on two different hosts.

What I do not understand is how the lifetime of the id_token is affecting the solution. Normally, the id_token has a five minute lifetime and it is only required to be valid during the handshake where the client application establishes it's session. The client application session can survive past the lifetime of the id_token.

To me, it looks like there's too many things happening on the same time in your setup and there might be some non-standard behaviour that makes it harder to understand. Considering you did inherit this solution, I would suggest moving over to our consulting services to get some help reviewing the solution.

@RolandGuijt
Copy link

@MSDev52 Please let us know if you have any follow up questions. If not I'd like to close this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants