Corporate authentication provider extension

Hi,

I am working on a new provider to connect to our corporate environment using OpenID Connect and OAuth 2. I am consulting this project https://github.com/OctopusDeploy/OpenIDConnectAuthenticationProviders to do so but there a few things that I don’t understand. Where is the ‘authenticateLink’ in the Authentication Provider class set? Also, are you planning on adding any integration tests to this project so that we can test new providers?

Kind regards,

Vangelis

Hi Vangelis,

Thanks for getting in touch, it’s great to hear that the extension functionality is being put to use and sorry that some of our documentation isn’t up to date yet. This is our initial step into providing extensibility in the server, and there may be a little volatility as we iron out some of the wrinkles. To that end, we are in the process of making some updates to the providers and the hosting setup to fix some issues that arose with the new providers when the server was behind a reverse proxy or load balancer. So let me first answer your questions based on the code you’re currently working from and then I’ll explain some of the changes (which actually went into the repos for the providers last night our time and is in the process of being tested in the server as a matter of priority).

So firstly, the authenticationLink is a property on an Angular controller that we wrap around the UI that the provider returns. It handles adding the redirectTo parameter to the “Authenticate” link returned in the AuthenticationProviderElement from the provider. Regarding the integration testing, unfortunately we don’t have a robust way of doing this yet, but is something we would certain like to have in the future.

Documentation around this was coming, and we’ve increased the priority on getting this done. It will cover the updated setup.

Which brings us to Extensibility v2.0. The core part of what’s changed is that the server no longer builds any absolute paths, because behind a reverse proxy that doesn’t work. So as an example, in the UserAuthenticationAction that you’ve been looking at you’ll see

var directoryPathResult = context.Request.AbsoluteVirtualDirectoryPath();

and that’s used later in a call to the UriBuilder to come up with the parameter to be passed to the external provider as the redirect_uri. We’ve changed that so the client passes us the URL from it’s perspective, which prevents the issues with the reverse proxy changing the request, and the server returns that rather than redirecting. The client does the redirect once it gets the values back.

To remove some of the mystery around what’s happening on the javascript side, the updated providers now use Angular modules to return the UI code. This way you can see exactly what’s happening, and can change it if you need to. There are a couple of values that can be bound into the directive and the doco will explain in detail what each is for. The doco is underway but it not yet publicly visible, once it is you’ll be able to find it in the Server Extensibility section of our doco.

I hope this helps. As I mentioned we’re working on getting the server tested and released as a priority, so hopefully it shouldn’t be too long. If I can be of further assistance please let me know.

Shannon

Hi Shannon,

Thank you very much for your prompt reply. I understand this is still a new project for you, but any help to get it working is appreciated.

I haven’t upgraded to the latest version of the extension functionality for now. I copied the .dll of my authentication provider to the BuiltInExtensions folder and enabled it from the command line successfully. The correct configuration record has been added in the ExtensionConfiguration table in the database. However, when I click on the added link on the login page it redirects to https://localhost/api/users/authenticate/?redirectTo=https%3A%2F%2Flocalhost%2Fapp%23 and it errors which seems like it cannot find the assembly. In the SQl Server profiler it looks like it’s querying the Configuration table with id WebPortal but not the ExtensionConfiguration table as well which seems to be the case when the Google Apps extension is enabled. Can you explain a bit when the page navigates to the link above please?

Thanks,

Vangelis

Hi Vangelis,

I’d recommend using the CustomExtensions folder discussed in our documentation to “install” your custom extensions. While they will work from the BuiltInExtensions folder, but the installers for upgrades may remove files from there that aren’t in our packages.

Just to check, where “” appears, is that literally what’s in the URL or just what you’ve sanitised the URL to? I’ll assume the latter, in which case the URL looks fine. Are you able to send the details of the error you are seeing?

Regarding what happens when you navigate to that link, I’m not sure exactly what level of detail you’re after so I’ll try to aim for somewhere in the middle and if you want more detail on any specifics then let me know. In v1.0 of the extensions the API on that route (UserAuthenticationAction) reads the configuration from the ExtensionConfiguration table, to get the values like the Issuer and ClientID required for the OpenID Connect authentication process. It then builds the URL to the Issuer and sends a redirect back to the browser to send it to the Issuer. One of the values it sends is the redirect_uri, which identifies the API the Issuer should POST the user’s token back to upon successful authentication. That API (UserAuthenticatedAction) is responsible for validating the returned token and redirecting the user back to the correct page in the UI. In our providers the understanding of which page to return to is handled via a parameter to UserAuthenticationAction that is transferred to UserAuthenticatedAction via the state in the user’s token.

Shannon

Hi Shannon,

I work with Vangelis. We have installed the assembly in the CustomExtension directory and enabled it. The link to use our corporate identity provider is being rendered on the login page as

https://localhost/api/users/authenticate/ABC?redirectTo=https%3A%2F%2Flocalhost%2Fapp%23

Where ABC is the (sanitised) name for the authentication provider we have registered with Octopus. When you click the link on the sign in page. you can see the GET request being made by the browser, however the server responds with a 404 and the page displays the Octopus icon with Not found and the text “The resource you requested was not found.”

It seems that the API is not able to match the route to invoke our authentication provider. I have checked the Octopus server log and don’t see anything obvious.

Is there something else we are missing?

Kind Regards
Stuart

Hi Stuart,

Great to hear you’ve made progress. From what you’ve described it sounds like the Nancy Module that hosts your API isn’t quite right. The page is trying to navigate to a URL that can then forward it to the external provider; in the built in providers AzureADApi provides an example of this. Do you have a class similar to that for your provider? Being a NancyModule (which is what an OpenIDConnectModule inherits from) you shouldn’t have to do anything special, Nancy will automatically find it and config it when your Dll loads.

If you do have this class, it may be worth taking a dependency on ILog and do a log.Info call as the first line in the constructor, to validate that Nancy is creating the class.

Also, we’ve released v2.0 of the extensions since I spoke to Vangelis, so some info on that. If you upgrade Octopus Deploy to 3.7.3 or greater you’ll also need to update your extension to use the v2.0 packages for extensibility. The 2.0 bump was due to some breaking changes we had to make in the API between the server and the extensions, to fix issues customers were encountering when the server was behind a reverse proxy or load balancer. When you’re ready to upgrade to the 2.0 extensibility packages, the changes you’ll need to make are along the lines of

  • changed method signatures. We dropped the parameter that the server used to pass through to the extensions to define the base URL for the site (this is the value that was incorrect when behind a reverse proxy). We use the “~” prefix to cope with virtual directories, if you’re looking at our code as examples. Given you aren’t using a virtual directory you could simply return a relative path in the methods where that parameter has been removed, e.g. return "styles/ABC.css"; would work fine.

  • the Angular directive that hosts your UI has been greatly simplified, and we changed the process a little to make things more visible to you. We have some updated documentation regarding authoring your own provider that will hopefully help explain more. Key point is that the API doesn’t do the redirect itself anymore, it passes info back and the UI does the redirect. For a code example of an extension Angular module have a look at https://github.com/OctopusDeploy/OpenIDConnectAuthenticationProviders/tree/master/source/Octopus.Server.Extensibility.Authentication.AzureAD/Web/Static/areas/users.

    • As a note on that example, the ~ and resolveLink function in the controller and directive are again because we need to support virtual directories and reverse proxies. You can remove those complications if you don’t need them.

Hope all that helps and let me know how you go with the NancyModule.

Shannon

Hi, thanks for the quick response. You were spot on. We missed implementing a class that implements the Api module. I added it and we get past that point. The next issue I see when I click on the provider link is

{
“ErrorMessage”: “Could not load type ‘Octopus.Server.Extensibility.HostServices.Web.DirectoryPathResult’ from assembly ‘Octopus.Server.Extensibility, Version=1.0.32.0, Culture=neutral, PublicKeyToken=null’.”
}

It is getting late so I will investigate further tomorrow but any insights or thoughts you can share may give us a head start,

Thanks
Stuart

Hi Stuart,

That class was added in Octopus.Server.Extensibility 1.1, so it’s most likely that your extension is being built against 1.1 of the nuget package but the Octopus Deploy server is using 1.0.32. If you update the package version you are building against that should resolve it.

Shannon

I have been been trying to find time today to continue to look into this and manage to find a little time.

We have a dev instance of Octopus which I upgraded to 3.7.6. I then updated the packages in our custom auth provider to point at the v2 packages and made all the amendments to add the angular directives and controllers following the same pattern as the v2 googleApps provider.

When I click on the link or browse to the authenticate end point directly

https://localhost/api/users/authenticate/ABC

An error is returned.
{
“ErrorMessage”: “Object reference not set to an instance of an object.”
}

Looking at the server logs this is being thrown in the UserAuthenticationAction base method. There is a point where the context is being bound to the model. This is returning a null model and when the RedirectAfterLoginTo property is accessed it goes bang.

There must be simple that I am missing but I have looked but can’t spot it.

Stu

Hi Stuart,

I thought I saw this happen to me the other day, but when I retried it didn’t happen. I thought it was only in IE, but I’ve just retested again on both IE and Chrome and not getting any issues.

Remember that that API is setup as a HTTP POST, which means a GET request by navigating through the browser will result in a null. I use Google’s Postman REST Client for manually testing the API endpoints, it lets you GET, POST and can also set Headers etc easily if you want to.

It’d be worth watching the Network tab in the browser dev tools (F12 if you’re using Chrome) and looking at the request that’s being sent. If it isn’t a POST and/or there isn’t a Json object containing ApiAbsUrl and RedirectAfterLoginTo in the Request Payload then the issue will be somewhere in the javascript.

Shannon

Hi Shannon,

We have progressed a little further but still having issues. I am at the point where the redirect to the external auth provider is working and we are being authenticated and receiving the post back.

Looking at the request, when the response_type is token+id_token I am receiving back the code, token (aka access token) and id_token. However I am getting an error in the call to ValidateToken.

The code in OpenIDConnectAuthTokenHandler will use the token if present in preference to the id_token for validation. I have added some logging to the OpenIDConnect project and see that the certificates are being retrieved from the certificate end point. The issue seems to be that the certificate is not being found in the dictionary of certificates retriever. I created a private resolver and added some logging to see what was going on. The identifier passed to the resolver is empty so the certificate can not be found in the dictionary. I am not sure where this comes from.

I also modified the code slightly to try to use the id_token instead of the token for purpose of the validation and the retrieval of the principle but without success. My understanding is that the claims are included in the id_token so I am not sure whether the code will work if both the token and id_token are provided. I tried changing the reponse_type to code+id_token but our identity provider then only returned the code.

I was able to grab the id_token from the request and decode it in a test project and parse each element and could see the head, payload and signature.

It feels like we are nearly there but still having issues.

Stuart

Hi Stuart,

Regarding the claims, they are included in the “token” (usually id_token but can depend on your configuration). You should be able to see them if you put the token content into https://jwt.io/

The identifier passed to the resolver should be the kid (key id) from the token. What should happen is the token should contain a kid that refers to a certificate that the certificate retriever is able to download, using OpenID Connect discovery (~/.well-known/openid-configuration).

Could you confirm which identity provider you are using? Is it possible that it doesn’t have X509 certificates configured?

Shannon