Jump to content

Topic on Project:Support desk/Flow

Changing to another OIDC provider

17
Summary by Cindy.cicalese

Fixed in OpenID Connect version 7,0.2.

Vajdaz (talkcontribs)

I use the OIDC Connect extension, and I would like to change my OIDC provider. In my LocalSettings.php I have set $wgOpenIDConnect_MigrateUsersByEmail to true. After changing the relevant settings (clientID, cientsecret, providerURL and preferred_username), I can login, but a new user is created with the username User (User1, User2, etc. in case of other users) and MediaWiki does not recognize that the users are the same (even the preferred_username values and emails retrieved from the id_token are the same). I also tried to delete the contents of the openid_connect table (docu says for $wgOpenIDConnect_MigrateUsersByEmail that "ff a user already exists in the database with the same email address as the authenticated user and has null values for subject and issuer, use this user, setting the subject and issuer in the database to those of the authenticated user. This is useful when the wiki previously used a different authentication mechanism.").

Here is my configuration:

$wgPluggableAuth_Config['Blabla'] = [

    'plugin' => 'OpenIDConnect',

    'data' => [

        'providerURL' => 'https://my-new-provider/',

        'clientID' => 'my-new-client-id',

        'clientsecret' => 'my-new-client-secret',

        'preferred_username' => 'something-company-specific',

        'scope' => [ 'openid', 'profile', 'email' , 'something-company-specific' ],

    ]

];

$wgOpenIDConnect_UseRealNameAsUserName = false;

$wgOpenIDConnect_UseEmailNameAsUserName = true;

$wgOpenIDConnect_MigrateUsersByUserName = false;

$wgOpenIDConnect_MigrateUsersByEmail = true;

$wgOpenIDConnect_ForceLogout = false;

$wgOpenIDConnect_RedirectURI = 'https://my.wiki.installation/index.php/Special:PluggableAuthLogin';


I also checked, that "email" and "something-company-specific" claims can be found in the id_token.

Wiki Version: 1.39.4

OpenID Connect Version: 7.0.1

PluggableAuth Version: 7.0.0

What I am doing wrong?

Cindy.cicalese (talkcontribs)

See the related discussion at Topic:Xj2hghe9wuq1s2o2. Deleting the rows in the openid_connect table and setting $wgOpenIDConnect_MigrateUsersByEmail = true; should work provided that the user table contains an email address for the desired user and the email claim is correct and matches that email address. You could check the debug log to track the login flow. See Manual:How to debug. Look for lines beginning with PluggableAuth and OpenIDConnect in the debug log.

Vajdaz (talkcontribs)

The logs show that the Email could not be determined correctly. The log line is like:

[OpenIDConnect] Real name: My Name, Email: , Subject: xxxxxxxxxxxx, Issuer: https://my.identity.provider/

Then later:

[OpenIDConnect] No user found with matching subject and issuer.

[OpenIDConnect] Using gid attribute for preferred username.

[OpenIDConnect] Preferred username:

[OpenIDConnect] Available username: User5

[PluggableAuth] Authenticated new user: User5

This looks like the fields could not be read from the id_token correctly. But I am quite sure that the token contains this data. I walked through the code flow manually and analyzed the received token. Both, email and gid were included in the id_token of the Bearer token that I got from the identity provider (which is AzureAD, if that may be worthful information).

How can I analyze in more depth what the extension extracts from the token and where the data gets lost?

Cindy.cicalese (talkcontribs)

Agreed that it does not seem to be returning the email correctly. The logging statement you show comes from https://gerrit.wikimedia.org/r/plugins/gitiles/mediawiki/extensions/OpenIDConnect/+/6b550e6603182c3bb1c5f943154e998fb483c34b/includes/OpenIDConnect.php#232. You could try also logging the following objects to see what is being received:


$oidc->requestUserInfo()

$oidc->getAccessTokenPayload()

$oidc->getIdTokenPayload()


That should hopefully give you more of an idea of what the issue might be.

Vajdaz (talkcontribs)

Lots of thanks for the hints. I finally could identify the problem. The authentication implementation does not use the id_token from the Bearer token to retrieve user data. A call to $oidc->requestUserInfo() requests the user info endpoint to provide the requested field (if provided in the optional argument) or the whole set of user data from the response. It turns out, that in the response of my new AzureAD user info endpoint the email field is missing. The token got from the code flow authentication contains it.

However, my email address can be found in the fields "upn" and "unique_name" of the response. Unluckily there is no way to map the email info to another field.

Is this a misconfiguration of my identity provider? Or is this a flaw in the authentication algorithm? With Jenkins I did not had such an issue when migrating to AzureAD. Seems that Jenkins retrieves the info about an authenticated user from the id_token received from the identity provider during the authentication, not via requests to the user info endpoint afterwards.

Cindy.cicalese (talkcontribs)

It does sound like a misconfiguration of your identity provider to me.

Vajdaz (talkcontribs)

I found this:

https://learn.microsoft.com/en-us/azure/active-directory/develop/userinfo


"Consider using an ID token instead The information in an ID token is a superset of the information available on UserInfo endpoint. Because you can get an ID token at the same time you get a token to call the UserInfo endpoint, we suggest getting the user's information from the token instead of calling the UserInfo endpoint. Using the ID token instead of calling the UserInfo endpoint eliminates up to two network requests, reducing latency in your application."

or

"To customize the information returned by the identity platform during authentication and authorization, use claims mapping and optional claims to modify security token configuration."

Which I interpret as Microsoft says, if you want performant and customizable claim usage, use the id_token durindmg authentication/authorization.

Is there any chance to change this in OIDC Connect? Otherwise I see no chance to make it work with the current setup of ny identity provider.

Other projects seem to have the same issue with Microsoft:

https://github.com/penpot/penpot/issues/1460

Or look at this:

https://github.com/MicrosoftDocs/azure-docs/issues/62965

Question: "So does that mean that the Graph OIDC Userinfo Endpoint Will not Return the optional claims mapped in the app manifest? The clients will be using the OIDC Userinfo Endpoint to validate what roles they claims for"

Answer: "That's correct. Only the ID token and access token contain that information. If they wish to collect roles via api call however they can call Graph for that information."

Vajdaz (talkcontribs)

I am not a php hacker, but looking on the code I asked myself, what would be if using getVerifiedClaims() instead of requestUserInfo()?

Cindy.cicalese (talkcontribs)

That actually sounds reasonable. Did you try it?

Vajdaz (talkcontribs)

Yes, I just did. With following patch it worked with AzureAD. And this change saves a couple of http requests. If you are unsure which claims can be found in the ID token and which one you can request only from user info endpoint, you could make a separate function, that first tries to retrieve the value from the ID token (with getVerifiedClaims()) and if the return value is null, it falls back to requestUserInfo(). That would make thinks more performant and more robust.


diff --git a/includes/OpenIDConnect.php b/includes/OpenIDConnect.php

index e5526b5..89effca 100644

--- a/includes/OpenIDConnect.php

+++ b/includes/OpenIDConnect.php

@@ -220,10 +220,10 @@ class OpenIDConnect extends PluggableAuth {

                        $this->getLogger()->debug( 'Redirect URL: ' . $redirectURL );


                        if ( $oidc->authenticate() ) {

-                               $realname = $oidc->requestUserInfo( 'name' );

-                               $email = $oidc->requestUserInfo( 'email' );

+                               $realname = $oidc->getVerifiedClaims( 'name' );

+                               $email = $oidc->getVerifiedClaims( 'email' );


-                               $this->subject = $oidc->requestUserInfo( 'sub' );

+                               $this->subject = $oidc->getVerifiedClaims( 'sub' );

                                $this->authManager->setAuthenticationSessionData( self::OIDC_SUBJECT_SESSION_KEY, $this->subject );


                                $this->issuer = $oidc->getProviderURL();

@@ -369,9 +369,9 @@ class OpenIDConnect extends PluggableAuth {

                if ( $this->getData()->has( 'preferred_username' ) ) {

                        $attributeName = $this->getData()->get( 'preferred_username' );

                        $this->getLogger()->debug( 'Using ' . $attributeName . ' attribute for preferred username.' . PHP_EOL );

-                       $preferred_username = $oidc->requestUserInfo( $attributeName );

+                       $preferred_username = $oidc->getVerifiedClaims( $attributeName );

                } else {

-                       $preferred_username = $oidc->requestUserInfo( 'preferred_username' );

+                       $preferred_username = $oidc->getVerifiedClaims( 'preferred_username' );

                }

                if ( is_string( $preferred_username ) && strlen( $preferred_username ) > 0 ) {

                        // do nothing

Vajdaz (talkcontribs)

Something like this (this one I did not test):


diff --git a/includes/OpenIDConnect.php b/includes/OpenIDConnect.php

index e5526b5..58e7fd1 100644

--- a/includes/OpenIDConnect.php

+++ b/includes/OpenIDConnect.php

@@ -220,10 +220,10 @@ class OpenIDConnect extends PluggableAuth {

                        $this->getLogger()->debug( 'Redirect URL: ' . $redirectURL );


                        if ( $oidc->authenticate() ) {

-                               $realname = $oidc->requestUserInfo( 'name' );

-                               $email = $oidc->requestUserInfo( 'email' );

+                               $realname = $oidc->getAttributeValue( 'name' );

+                               $email = $oidc->getAttributeValue( 'email' );


-                               $this->subject = $oidc->requestUserInfo( 'sub' );

+                               $this->subject = $oidc->getAttributeValue( 'sub' );

                                $this->authManager->setAuthenticationSessionData( self::OIDC_SUBJECT_SESSION_KEY, $this->subject );


                                $this->issuer = $oidc->getProviderURL();

@@ -369,9 +369,9 @@ class OpenIDConnect extends PluggableAuth {

                if ( $this->getData()->has( 'preferred_username' ) ) {

                        $attributeName = $this->getData()->get( 'preferred_username' );

                        $this->getLogger()->debug( 'Using ' . $attributeName . ' attribute for preferred username.' . PHP_EOL );

-                       $preferred_username = $oidc->requestUserInfo( $attributeName );

+                       $preferred_username = $oidc->getAttributeValue( $attributeName );

                } else {

-                       $preferred_username = $oidc->requestUserInfo( 'preferred_username' );

+                       $preferred_username = $oidc->getAttributeValue( 'preferred_username' );

                }

                if ( is_string( $preferred_username ) && strlen( $preferred_username ) > 0 ) {

                        // do nothing

@@ -450,4 +450,13 @@ class OpenIDConnect extends PluggableAuth {

                }

                return null;

        }

+

+       private function getAttributeValue( $attributeName ) {

+               $value = $oidc->getVerifiedClaims( $attributeName );

+                if ( $value ) {

+                        return $value;

+                }

+                return $oidc->requestUserInfo( $attributeName );

+       }

+

}

Cindy.cicalese (talkcontribs)

Looks good. Would you be wiling to submit the patch to gerrit? If you have not done so before, see Gerrit/Tutorial/tl;dr.

Vajdaz (talkcontribs)
Cindy.cicalese (talkcontribs)

Thank you!

Yes, if it is merged, I will backport it to the REL1_39, REL1_40, and REL1_41 branches.

Cindy.cicalese (talkcontribs)

The current test failure is unrelated to the patch. I will fix it in another patch then rebase this one - hopefully in the next few days. I will also test this patch and merge/backport if I can confirm it works for other sites.

In the meantime, could you please create a task related to this in Phabricator and link to this discussion? You can create it in the backlog of https://phabricator.wikimedia.org/project/view/862/. Then, please update the commit message in gerrit to add:


Bug: TXXXXXX


before the Change-Id line, where TXXXXXX is the number of the new task.


Thank you!

Vajdaz (talkcontribs)

For people with a similar problem, who land here googling for a solution: OpenID Connect extension 7.0.2 contains a fix that makes authentication work with Microsoft AzureAD, too.

Thanks, Cindy, for supporting me!

Cindy.cicalese (talkcontribs)

Thank you for providing a solution!