Tech Spec - OpenId Connect

OVERVIEW

We currently support three OAuth Providers, GitLab, Google, and Office365. After researching our implementation and OpenId Connect, Mattermost already supports most of OpenId Connect. Review the background reading in order to understand OpenId Connect and its relationship to OAuth. As well as the login flow for OAuth.

TLDR:

  • OpenId Connect uses the OAuth2 protocol

  • OpenId Connect is the Authentication process for OAuth2 for Google and some other providers.

OpenId Connect is built on the OAuth 2.0 protocol and uses an additional JSON Web Token (JWT), called an ID token, to standardize areas that OAuth 2.0 leaves up to choice, such as scopes and endpoint discovery. It is specifically focused on user authentication and is widely used to enable user logins on consumer websites and mobile apps.

OpenId Connect is used for Authentication. OAuth is used for Authentication and Authorization. Using OAuth, a user can be Authenticated that they have an account on google. A user could also Authorize an application to access their data on Google, such as access to their calendar.

Each OAuth implementation support their own scopes. Scopes are the Authorization you are requesting from the user. For example, our current OAuth implementation requires access to user information. Different scopes are requested based on the OAuth provider.

  • Google - [email profile]

  • Office365 - [read.me]

  • GitLab - [api, read_user, read_api]

OpenId Connect standardizes on the following scopes [email openid profile]

In addition, retrieving user information is not standard using OAuth. This is the reason different implementations are required for each OAuth provider we support. The different endpoints are handled via configuration settings, but parsing the returned JSON requires knowledge of the format being returned.

OpenId Connect uses the userinfo_endpoint property in the Discovery Document to determine the endpoint. The OpenId Connect Spec defines the standard claims returned from this endpoint. Not all claims are returned for every provider. Therefore, checks for required fields and perhaps parsing a Name property will be necessary. The claims returned are defined in the providers Discovery Document.

  • Do multiple providers need supported? I don’t see a technical reason we couldn’t. Currently, we only support a single OAuth provider. OAuth + OpenId?

    • May require login form rework.

  • Discovery Document is not REQUIRED by the OpenId Spec. Do we want to require it or allow users to enter endpoints individually as well?

GOALS

  1. Support OpenId Connect standard for Authentication

  2. Support Discovery Document

  3. Support retrieving user fields currently supported in OAuth2 implementations

    1. Either from IdToken or additional userinfo_endpoint call

    2. Email, First Name, Last Name, Username (from Email), Nickname, AuthData

  4. Ensure OpenId Connect works with existing OAuth providers. (Office365, Google, GitLab)

  5. Migrate Existing OAuth2.0 providers to OpenId Connect

SCOPE

In:

  • OpenId Connect Authentication Provider

  • System Console Setup for OpenId Connect Provider

  • Support of OpenId Connect in Mobile/Desktop/WebApp

Out:

  • Google Support on Mobile

  • Multiple Provider Support

  • Plugin Interactivity

BACKGROUND READING

Google’s OpenId Connect / OAuth2 implementation and the differences between them.

OpenId Connect Specification

TERMINOLOGY

OpenID Provider - OAuth 2.0 Authorization Server that is capable of Authenticating the End-User and providing Claims to a Relying Party about the Authentication event and the End-User.

IdToken - [JWT] that contains Claims about the Authentication event. It MAY contain other Claims.

JWTToken - a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JavaScript Object Notation (JSON) object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or MACed and/or encrypted.

SPECIFICATIONS

High-level Architecture

At a high-level, the OpenId provider will work in the same manner as the current OAuth2 implementation.
Although most of the code for performing the OAuth authorization already exists, it will need to be refactored in order to be used by the OpenId Connect provider. For instance, GetAuthorizationCode() retrieves the Endpoint information from the config file. The OpenId Connect provider will retrieve that information from the Discovery Document.

The steps are provided below with where that code exists for the existing OAuth implementation.

The steps below will be preceded by a call to the OpenId Provider’s Discovery Document Endpoint. That call should only be required once and the results cached from there.

  1. Mattermost sends a request to the OpenID Provider (OP). [AuthorizationEndpoint]
    When creating the call to the authorization endpoint, a token gets created and stored, this allows Mattermost to verify it was the provider calling back. [GetAuthorizationCode][app/oauth.go]

  2. The OP authenticates the End-User and obtains authorization.
    The user is presented with a login form from the OP, then an authorization for the requested scopes.

  3. The OP responds with a Token.
    Mattermost validates the token, by retrieving it from the database and comparing Token information.(AuthorizeOAuthUser)[app/oauth.go]

  4. Mattermost send a request to the OpenId Provider. [TokenEndpoint]
    Mattermost exchanges secret for AccessToken/ID Token (AuthorizeOAuthUser)[app/oauth.go]

  5. The OP responds with an ID Token and usually an Access Token.
    The ID Token gets verified and decoded. [New functionality]
    Some providers pass all the user info in the Id Token (Google, Exchange), therefore the login process could end here. Others [GitLab] do not return user information in the Id Token and require an additional call. [New Functionality]

  6. Mattermost requests with the Access Token to the UserInfo Endpoint.
    Similar to existing call to UserApiEndpoint (AuthorizeOAuthUser)[app/oauth.go]

  7. The UserInfo Endpoint returns Claims about the End-User.
    Convert Claims to model.User [New]
    Similar to OAuth Providers [userFromGoogleUser]

Discovery Document

The OpenID Connect protocol requires the use of multiple endpoints for authenticating users, and for requesting resources including tokens, user information, and public keys.

To simplify implementations and increase flexibility, OpenID Connect allows the use of a "Discovery document," a JSON document found at a well-known location containing key-value pairs which provide details about the OpenID Connect provider's configuration, including the URIs of the authorization, token, revocation, userinfo, and public-keys endpoints. The Discovery document for Google's OpenID Connect service may be retrieved from:

Google
https://accounts.google.com/.well-known/openid-configuration

GitLab
https://[gitlab instance]/.well-known/openid-configuration
https://gitlab.com/.well-known/openid-configuration

Office365
https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration
https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration

All other required endpoints can be retrieved from this document. The ones required by our implementation are:

{ "issuer": "https://accounts.google.com", "authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth", "token_endpoint": "https://oauth2.googleapis.com/token", "userinfo_endpoint": "https://openidconnect.googleapis.com/v1/userinfo", "jwks_uri": "https://www.googleapis.com/oauth2/v3/certs", ...}

issuer - The IDP of the provider

authorization_endpoint - request for user to login and authorize.

token_endpoint - exchange login token for access token

userinfo_endpoint - endpoint to retrieve user information.

jwks_uri - endpoint for certificates to verify JWTToken

JWT Token

The JWT Token is returned from the call to the token_endpoint,when the OpenId scope is provided. This token needs to be parsed and verified. Although it is possible to parse the token without verifying it. Verification is necessary from a security perspective.

For details on verifying the parsing the JWTToken, see Validating an ID token. Another good source for using Go and validating with a library, see here.

For ease of development, using a library for this validation is the best approach. However, some time will need to be used in order to determine which library to use, there are several.

UserInfo_Endpoint

The userinfo_endpoint is an endpoint that can be called to retrieve additional metadata about the user. This would replace the UserApiEndpoint in the current OAuth2 implementation. For some providers, such as google. All the user information we currently retrieve and save is provided in the JWT Token. However, GitLab only returns basic information in the token, requiring a call to the user endpoint.

Although the field names are consistent across various providers, not all fields are returned by all providers.

model.User Property

GoogleUser

IdToken(google)

userInfo (google)

IdToken (github)

userInfo (github)

userInfo (Office365)

model.User Property

GoogleUser

IdToken(google)

userInfo (google)

IdToken (github)

userInfo (github)

userInfo (Office365)

Email

Email

email

email

 

email

email

Username

From Email

from Email

from email

 

from email

from email

FirstName

GivenName

given_name
name (Split space)(check 3 names?)

given_name
name split space

 

name split space

name split space

LastName

FamilyName

family_name
name (Split space)(check 3 names?)

family_name
name split space

 

name - split space

name split space

Nickname

Nicknames[0]

nickname

nickname(?)

 

nickname

nickname

AuthData

MetadataSource.Id

sub

sub

sub

sub

sub

AuthService

GOOGLE

"iss":"https://accounts.google.com"

 

"iss":"https://gitlab.com"

 

 

 

 

picture (url)

locale

iat (time issued)
expires

profile

picture

iat

profile

picture

iat

profile

picture

iat

 

Licensing

The OpenId Connect provider is planned as a E20 feature. The feature should be protected by license checks.

Permissions

There should not be any permission changes

Schema

No Schema Changes. New AuthType in Users table, but that is set in server and a simple string in the database.

REST API

There is the potential of adding additional api commands for handling OpenId Connect.

w.MainRouter.Handle("/openid/{service:[A-Za-z0-9]+}/complete", w.ApiHandler(completeOAuth)).Methods("GET") w.MainRouter.Handle("/openid/{service:[A-Za-z0-9]+}/login", w.ApiHandler(loginWithOAuth)).Methods("GET") w.MainRouter.Handle("/openid/{service:[A-Za-z0-9]+}/mobile_login", w.ApiHandler(mobileLoginWithOAuth)).Methods("GET") w.MainRouter.Handle("/openid/{service:[A-Za-z0-9]+}/signup", w.ApiHandler(signupWithOAuth)).Methods("GET")

Ideally, OpenId could be handled as an additional service and with the existing code. However, that will depend on whether multiple providers are allowed. And how easily the code can be refactored to accommodate both.

CLI

No CLI changes required

Configuration

"OpenIdConnectSettings":{ "Enable": false, "Secret": "3d4651465eff9493271e9146de8ff6d658b09787aaa873ff03533188b68722da", "Id": "4aee457b0176327fd58aae5339cd7dabdd1c2d36637d1b8766a6e25da94e3622", "DiscoveryDocumentEndpoint": "https://accounts.google.com/.well-known/openid-configuration" }

Webapp only

System Console

https://auth0.com/docs/connections/enterprise/oidc#create-an-enterprise-connection-in-auth0
https://fusionauth.io/docs/v1/tech/identity-providers/openid-connect/

This will be a new Authentication Type with its own Subheading.

The following configuration settings will need to be retrieved from the user.

  • Application ID

  • Application Secret Password

  • Discovery Document Url

Mobile and Webapp

  • Account Settings

    • Restrict user from setting

      • Username

      • Email

      • Similar to other OAuth providers

  • Logging In

    • WebApp -

      • login_controller.jsx

      • Add OpenIdConnect section

        • Is user restricted to OpenId Connect or OAuth?

        • Currently OAuth limited to one being turned on at a time.

        • I don’t think it is a technical limitation.

    • Mobile

      • loginOptions.js

      • Add OpenIdConnect Option

      • Google OAuth not supported on mobile,

        • Need to understand why

        • Will this affect OpenIdConnect?

      • Update to login flow

Mobile Migration -
w/ old server

  • New OpenId won’t be available as server won’t support.

  • Existing OAuth Connections will continue to work as is.

w/ new server

  • New OpenId will be available if configured and enabled on the server.

  • Existing OAuth Connections will continue to work as is.

    • Same endpoints, change is on server side.

Old app w/ new server

  • New OpenId will not be available

    • If that is users login method, they would not be able to login.

  • Existing OAuth Connections will continue to work as is.

Migration

Once an OAuth System has been migrated to OIDC, we need to understand the impact on existing users. Users will remain logged in as long as their session is valid. Once their session expires, the users will go through the new login process. They will be asked to login in their provider (or not, if already logged into their provider). Whether or not the users are asked to reauthorize usage depends on each Provider.

Each of these was tested as follows -

  • Remove Authorized Application from Provider

  • Login using current OAuth Settings

  • Change Configuration to use OpenId Connect for same provider, same application

  • Login using OpenId Connect Scopes [OpenId, Email, Profile]

Google

Google already uses OpenId Connect as the login mechanism for OAuth. The user does not have to authorize any additional access.

GitLab

GitLab controls the user’s allowed scopes in the GitLab Application. The admin will need to add or verify users have access to [email profile openid] scopes in the Mattermost application on GitLab. Once that is done when the user logs in, the user will be asked to authorize access to the new scopes.

Office365

Office’s User.Read permission allow MM to “Read your profile”. That single permission is enough to allow the profile, email, openid scopes to operate successfully. If the application is deleted and recreated the “View your basic profile” and “View your email address” permissions request access.

Performance

The retrieval of the Discovery Document should only happen once and then be cached.

Plugins

No immediate impact on plugins. However, we need to explore the use of the IdToken to be used between plugins and the server for authentication. Need to understand the plugin use cases better.

I do not understand how plugins use OAuth. Currently, they authenticate separately, I believe. But in the future, we want to look at using the IdToken between the server and plugins. There is a way for applications to use incremental authorization to request additional permissions. However, that is beyond the scope of this document.

CREDITS

Thanks to