Login using OpenID

When I was fiddling around with this I’ve found two options:

  • have the identity server in a different process, and copy the identity from there to the identity server in squidex. In this case your external identity server is an external provider, and it is displayed on the login screen, and when the user clicks on it, the system should build a redirect url, where you receive the user details from the external identity server. Based on these external user details the system adds a new user to the db, and generates its own token for the user. In case of google and the others currently squidex does this thing, and I think we call this identity brokering.
  • have the identity server in a different process, remove identity server from squidex, and set the authentication scheme to JWTBearer. In this case squidex will act as a resource of your external identity server, and you need to rewrite the frontend of squidex to authenticate through your external identity server and get the tokens from there.

I guess you managed to do the option one from the above list, and now only problem is that you get signed out after logged in. In that case check the local storage, because squidex frontend is using oidc js lib to authenticate with the backend, and that one saves the token in local storage. I had a similiar issue before, and the solution was some sort of config modification.

It depends where you want to store your user information.

if it is fine for you to have multiple user databases, you can keep identity server. If you want to have a single place for all your user data you can get rid of identity server.

Angular is caching the user inforrmation in the local store, but there is no way to disable it.

You are right I implemented the first option. I created my own Identity Server and is acting as an external login provider. I am putting the code here maybe it will be of interest to anyone.

AuthenticationService.cs

public static class AuthenticationServices
{
    public static void AddMyAuthentication(this IServiceCollection services, IConfiguration config)
    {
        var identityOptions = config.GetSection("identity").Get<MyIdentityOptions>();
        var urlsOptions = config.GetSection("urls").Get<MyUrlsOptions>();

        services.AddOidcStateDataFormatterCache();

        services
        .AddAuthentication()
        .AddOpenIdConnect("oidc", options =>
        {
            options.SignInScheme = IdentityConstants.ExternalScheme; // This is very important
            options.Authority = "http://localhost:5000";
            options.ClientId = "my-client";
            options.ClientSecret = Constants.InternalClientSecret;
            options.RequireHttpsMetadata = identityOptions.RequiresHttps;
            options.SaveTokens = true;
            options.GetClaimsFromUserInfoEndpoint = true;
            options.Scope.Add(Constants.ProfileScope);
            options.Scope.Add(Constants.RoleScope);
            options.Scope.Add("email");
            options.ResponseType = OpenIdConnectResponseType.IdToken;
            options.TokenValidationParameters = new TokenValidationParameters
            {
                NameClaimType = ClaimTypes.Name,
                RoleClaimType = ClaimTypes.Role,
            };
        })
        .AddCookie()
        .AddMyGoogleAuthentication(identityOptions)
        .AddMyMicrosoftAuthentication(identityOptions)
        .AddMyIdentityServerAuthentication(identityOptions, config);
    }
}

Client, Api Resources and Identity Resources on custom Identity Server

public static IEnumerable<ApiResource> GetApiResources()
    {
        return new List<ApiResource>
        {
            new ApiResource(ApiScope)
            {
                UserClaims = new List<string>
                {
                    JwtClaimTypes.Email,
                    JwtClaimTypes.Role,                     
                }
            }
        };
    }

    public static IEnumerable<Client> GetClients()
    {
        return new List<Client>
        {
            new Client
            {
                ClientId = "my-client",
                ClientName = "my-client",
                ClientSecrets = new List<Secret> { new Secret(InternalClientSecret) },
                RedirectUris = new List<string>
                {
                    "http://localhost:50006/portal/signin-oidc",
                    "http://localhost:50006/orleans/signin-oidc",
                    "http://localhost:50006/identity-server/signin-oidc"
                },
                AccessTokenLifetime = (int)TimeSpan.FromDays(30).TotalSeconds,
                AllowedGrantTypes = GrantTypes.ImplicitAndClientCredentials,
                AllowAccessTokensViaBrowser = true,
                AllowedCorsOrigins =     { "http://localhost:50006" },
                AllowedScopes = new List<string>
                {
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile,
                    IdentityServerConstants.StandardScopes.Email,
                    ApiScope,
                    ProfileScope,
                    RoleScope,
                },
                RequireConsent = false,
                AlwaysSendClientClaims = true
            }
        };
    }

    public static IEnumerable<IdentityResource> GetIdentityResources()
    {
        return new List<IdentityResource>
        {
            new IdentityResources.OpenId(),
            new IdentityResources.Profile(),
            new IdentityResources.Email(),
            new IdentityResource("role",
            new[]
            {
                JwtClaimTypes.Role
            }),
            new IdentityResource("squidex-profile",
            new[]
            {
                "urn:squidex:name",
                "urn:squidex:picture"
            })
        };
    }

I do not mind so I guess for now I can leave both Identity Servers as to be less intrusive to the original source code as I can.

This is very frustrating and I cannot wrap my head around it as it very difficult to achieve login / debug. Do you remember by any chance what config did you change?

Not really. I only remember that I’ve found the problem by installing an addon in firefox which outputs all the http traffic in stdout, so when I run firefox from command line I could see all the http traffic, and based on that I could figure out what is going wrong.

If you can provide a simple demo identity server I can also have a look.

@nicholas I had problems with my localdb and therefore I tested it with keycloak. The only config I need is:

        .AddOpenIdConnect("Keycloak", options =>
        {
            options.Authority = "http://localhost:8080/auth/realms/master";
            options.ClientId = "keycloak";
            options.ClientSecret = "db7a542b-69ed-43c6-af2a-5465f5a39769";
            options.RequireHttpsMetadata = identityOptions.RequiresHttps;
        })

I tried Auth0 and it works too. Let me give Keycloack a spin too. Will keep you posted.

If you use keycloak you have to ensure that the email is provided. You can just map the email to the profile scope under Mappings.

I also added the support for oidc to the code: https://github.com/Squidex/squidex/commit/f464844d5629f5ef8d35271c3046ccb3c22f18da

Keycloak works and so does Auth0. Only my Identity Server implementation (along with samples hosted by IdenityServer4) are not working.

Let us close this issue for now, if I find something I will make sure to update this ticket!

Thanks a million for your support on this and for updating the codebase. Much obliged!

You are welcome. Post closed.