diff --git a/Documentation/custom-scopes-claims-clients.md b/Documentation/custom-scopes-claims-clients.md
index 2c15137e430fa20f06d4f764c128627b749abd96..37f4f64ff4c0667ef05b12ed5e4610d5f211bf00 100644
--- a/Documentation/custom-scopes-claims-clients.md
+++ b/Documentation/custom-scopes-claims-clients.md
@@ -12,7 +12,7 @@ The following is the exhaustive list of scopes supported by dex:
 | `email` | ID token claims should include the end user's email and if that email was verified by an upstream provider. |
 | `profile` | ID token claims should include the username of the end user. |
 | `groups` | ID token claims should include a list of groups the end user is a member of. |
-| `offline_access` | Token response should include a refresh token. |
+| `offline_access` | Token response should include a refresh token. Doesn't work in combinations with some connectors, notability the [SAML connector][saml-connector] ignores this scope. |
 | `audience:server:client_id:( client-id )` | Dynamic scope indicating that the ID token should be issued on behalf of another client. See the _"Cross-client trust and authorized party"_ section below. |
 
 ## Custom claims
@@ -67,5 +67,6 @@ The ID token claims will then include the following audience and authorized part
 }
 ``` 
 
+[saml-connector]: saml-connector.md
 [core-claims]: https://openid.net/specs/openid-connect-core-1_0.html#IDToken
 [standard-claims]: https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
diff --git a/Documentation/dev-integration-tests.md b/Documentation/dev-integration-tests.md
index 8ed1e6efbc3e5d0c9a2629d7c7f8dbe1c780aaec..38d90c89fd83794ddbdb869df7cb4f55abc14fa8 100644
--- a/Documentation/dev-integration-tests.md
+++ b/Documentation/dev-integration-tests.md
@@ -121,7 +121,7 @@ At the app, go to the "Sign On" tab and then click "View Setup Instructions". Us
 
 ```yaml
 connectors:
-- type: samlExperimental
+- type: saml
   id: saml
   name: Okta
   config:
diff --git a/Documentation/saml-connector.md b/Documentation/saml-connector.md
index b0fbadba1a41afed5d52c21151ae1c455399a17b..62cf6a7ff500bd23b8e81be5c07a4fcb07617716 100644
--- a/Documentation/saml-connector.md
+++ b/Documentation/saml-connector.md
@@ -2,23 +2,23 @@
 
 ## Overview
 
-The experimental SAML provider allows authentication through the SAML 2.0 HTTP POST binding.
+The SAML provider allows authentication through the SAML 2.0 HTTP POST binding. The connector maps attribute values in the SAML assertion to user info, such as username, email, and groups.
 
 The connector uses the value of the `NameID` element as the user's unique identifier which dex assumes is both unique and never changes. Use the `nameIDPolicyFormat` to ensure this is set to a value which satisfies these requirements.
 
+Unlike some clients which will process unprompted AuthnResponses, dex must send the initial AuthnRequest and validates the response's InResponseTo value.
+
 ## Caveats
 
-There are known issues with the XML signature validation for this connector. In addition work is still being done to ensure this connector implements best security practices for SAML 2.0.
+__The connector doesn't support refresh tokens__ since the SAML 2.0 protocol doesn't provide a way to requery a provider without interaction. If the "offline_access" scope is requested, it will be ignored.
 
 The connector doesn't support signed AuthnRequests or encrypted attributes.
 
-The connector doesn't support refresh tokens since the SAML 2.0 protocol doesn't provide a way to requery a provider without interaction. Ensure that the "offline_access" scope is not requested in client apps.
-
 ## Configuration
 
 ```yaml
 connectors:
-- type: samlExperimental # will be changed to "saml" later without support for the "samlExperimental" value
+- type: saml
   # Required field for connector id.
   id: saml
   # Required field for connector name.
@@ -27,9 +27,23 @@ connectors:
     # SSO URL used for POST value.
     ssoURL: https://saml.example.com/sso
 
-    # CA to use when validating the SAML response.
+    # CA to use when validating the signature of the SAML response.
     ca: /path/to/ca.pem
 
+    # Dex's callback URL.
+    #
+    # If the response assertion status value contains a Destination element, it
+    # must match this value exactly.
+    #
+    # This is also used as the expected audience for AudienceRestriction elements
+    # if entityIssuer isn't specified.
+    redirectURI: https://dex.example.com/callback
+
+    # Name of attributes in the returned assertions to map to ID token claims.
+    usernameAttr: name
+    emailAttr: email
+    groupsAttr: groups # optional
+
     # CA's can also be provided inline as a base64'd blob.
     #
     # caData: ( RAW base64'd PEM encoded CA )
@@ -39,37 +53,31 @@ connectors:
     #
     # insecureSkipSignatureValidation: true
 
-    # Optional: Issuer value for AuthnRequest
-    # Must be contained within the "AudienceRestriction" attribute in all responses
-    # If not set, redirectURI will be used for audience validation
+    # Optional: Manually specify dex's Issuer value.
+    #
+    # When provided dex will include this as the Issuer value during AuthnRequest.
+    # It will also override the redirectURI as the required audience when evaluating
+    # AudienceRestriction elements in the response.
     entityIssuer: https://dex.example.com/callback
 
-    # Optional: Issuer value for SAML Response
+    # Optional: Issuer value expected in the SAML response.
     ssoIssuer: https://saml.example.com/sso
 
-    # Dex's callback URL. Must match the "Destination" attribute of all responses
-    # exactly.
-    redirectURI: https://dex.example.com/callback
-
-    # Name of attributes in the returned assertions to map to ID token claims.
-    usernameAttr: name
-    emailAttr: email
-    groupsAttr: groups # optional
-
+    # Optional: Delimiter for splitting groups returned as a single string.
+    #
     # By default, multiple groups are assumed to be represented as multiple
     # attributes with the same name.
     #
     # If "groupsDelim" is provided groups are assumed to be represented as a
     # single attribute and the delimiter is used to split the attribute's value
     # into multiple groups.
-    #
-    # groupsDelim: ", "
+    groupsDelim: ", "
 
-
-    # Requested format of the NameID. The NameID value is is mapped to the ID Token
-    # 'sub' claim.  This can be an abbreviated form of the full URI with just the last
-    # component. For example, if this value is set to "emailAddress" the format will
-    # resolve to:
+    # Optional: Requested format of the NameID.
+    #
+    # The NameID value is is mapped to the user ID of the user. This can be an
+    # abbreviated form of the full URI with just the last component. For example,
+    # if this value is set to "emailAddress" the format will resolve to:
     #
     #     urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
     #
@@ -78,8 +86,20 @@ connectors:
     #     urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
     #
     nameIDPolicyFormat: persistent
+```
+
+A minimal working configuration might look like:
 
-    # Optional issuer used for validating the SAML response. If provided the
-    # connector will validate the Issuer in the response.
-    # issuer: https://saml.example.com
+```yaml
+connectors:
+- type: saml
+  id: okta
+  name: Okta
+  config:
+    ssoURL: https://dev-111102.oktapreview.com/app/foo/exk91cb99lKkKSYoy0h7/sso/saml
+    ca: /etc/dex/saml-ca.pem
+    redirectURI: http://127.0.0.1:5556/dex/callback
+    usernameAttr: name
+    emailAttr: email
+    groupsAttr: groups
 ```
diff --git a/README.md b/README.md
index bac47f1a81f89fc7cb2ed3afe34788f1bffe8c60..28fc1530ccee34bf19372d1ff06e258ee4df3b51 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,7 @@ More docs for running dex as a Kubernetes authenticator can be found [here](Docu
   * [LDAP](Documentation/ldap-connector.md)
   * [GitHub](Documentation/github-connector.md)
   * [GitLab](Documentation/gitlab-connector.md)
-  * [SAML 2.0 (experimental)](Documentation/saml-connector.md)
+  * [SAML 2.0](Documentation/saml-connector.md)
   * [OpenID Connect](Documentation/oidc-connector.md) (includes Google, Salesforce, Azure, etc.)
 * Client libraries
   * [Go][go-oidc]
diff --git a/cmd/dex/config.go b/cmd/dex/config.go
index fef06c113e6241d892aec7bfe35133cbf277133a..d15f23c909d87282ad5e2ac2ac6de702d767163e 100644
--- a/cmd/dex/config.go
+++ b/cmd/dex/config.go
@@ -6,9 +6,9 @@ import (
 	"fmt"
 	"os"
 
+	"github.com/Sirupsen/logrus"
 	"golang.org/x/crypto/bcrypt"
 
-	"github.com/Sirupsen/logrus"
 	"github.com/coreos/dex/connector"
 	"github.com/coreos/dex/connector/github"
 	"github.com/coreos/dex/connector/gitlab"
@@ -179,12 +179,14 @@ type ConnectorConfig interface {
 }
 
 var connectors = map[string]func() ConnectorConfig{
-	"mockCallback":     func() ConnectorConfig { return new(mock.CallbackConfig) },
-	"mockPassword":     func() ConnectorConfig { return new(mock.PasswordConfig) },
-	"ldap":             func() ConnectorConfig { return new(ldap.Config) },
-	"github":           func() ConnectorConfig { return new(github.Config) },
-	"gitlab":           func() ConnectorConfig { return new(gitlab.Config) },
-	"oidc":             func() ConnectorConfig { return new(oidc.Config) },
+	"mockCallback": func() ConnectorConfig { return new(mock.CallbackConfig) },
+	"mockPassword": func() ConnectorConfig { return new(mock.PasswordConfig) },
+	"ldap":         func() ConnectorConfig { return new(ldap.Config) },
+	"github":       func() ConnectorConfig { return new(github.Config) },
+	"gitlab":       func() ConnectorConfig { return new(gitlab.Config) },
+	"oidc":         func() ConnectorConfig { return new(oidc.Config) },
+	"saml":         func() ConnectorConfig { return new(saml.Config) },
+	// Keep around for backwards compatibility.
 	"samlExperimental": func() ConnectorConfig { return new(saml.Config) },
 }
 
diff --git a/server/handlers.go b/server/handlers.go
index 5bd469c6d93a9c81b9c35ac3f8c256a5d48e3434..78eae26c57a95e0930fbb292e957c27a4d0b65a0 100644
--- a/server/handlers.go
+++ b/server/handlers.go
@@ -648,7 +648,7 @@ func (s *Server) handleAuthCode(w http.ResponseWriter, r *http.Request, client s
 	reqRefresh := func() bool {
 		// Ensure the connector supports refresh tokens.
 		//
-		// Connectors like `samlExperimental` do not implement RefreshConnector.
+		// Connectors like `saml` do not implement RefreshConnector.
 		conn, ok := s.connectors[authCode.ConnectorID]
 		if !ok {
 			s.logger.Errorf("connector ID not found: %q", authCode.ConnectorID)