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
Support OpenId Connect standard for Authentication
Support Discovery Document
Support retrieving user fields currently supported in OAuth2 implementations
Either from IdToken or additional userinfo_endpoint call
Email, First Name, Last Name, Username (from Email), Nickname, AuthData
Ensure OpenId Connect works with existing OAuth providers. (Office365, Google, GitLab)
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.
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]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.The OP responds with a Token.
Mattermost validates the token, by retrieving it from the database and comparing Token information.(AuthorizeOAuthUser)[app/oauth.go]Mattermost send a request to the OpenId Provider. [TokenEndpoint]
Mattermost exchanges secret for AccessToken/ID Token (AuthorizeOAuthUser)[app/oauth.go]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]Mattermost requests with the Access Token to the UserInfo Endpoint.
Similar to existing call to UserApiEndpoint (AuthorizeOAuthUser)[app/oauth.go]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:
Googlehttps://accounts.google.com/.well-known/openid-configuration
GitLabhttps://[gitlab instance]/.well-known/openid-configuration
https://gitlab.com/.well-known/openid-configuration
Office365https://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) |
---|---|---|---|---|---|---|
| ||||||
Username | From Email | from Email | from email |
| from email | from email |
FirstName | GivenName | given_name | given_name |
| name split space | name split space |
LastName | FamilyName | family_name | family_name |
| name - split space | name split space |
Nickname | Nicknames[0] | nickname | nickname(?) |
| nickname | nickname |
AuthData | sub | sub | sub | sub | sub | |
AuthService | "iss":"https://accounts.google.com" |
| "iss":"https://gitlab.com" |
|
| |
|
| picture (url) locale iat (time issued) | 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 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