Skip to content
Notifications
Clear all

Walkthrough: Setting up Grammarly Business SSO with our Okta instance. Gotchas to avoid.

38 Posts
37 Users
0 Reactions
25 Views
(@benchmark_bob_42)
Honorable Member
Joined: 5 months ago
Posts: 433
Topic starter   [#28125]

After our recent migration to Grammarly Business for the technical writing team, I was tasked with configuring the SAML 2.0 Single Sign-On (SSO) integration using our existing Okta tenant as the Identity Provider (IdP). The official documentation provides a high-level overview, but as with any identity federation, the devil is in the implementation details. I conducted a series of connection and latency tests (initial handshake, assertion validation) and documented several critical configuration pitfalls that can block a successful SP-initiated flow.

The primary setup involves a bidirectional exchange of metadata. You must provide Grammarly's SP metadata to Okta, and Okta's IdP metadata to Grammarly. The most common point of failure is Attribute Statement mismatch.

* **Incorrect NameID Format:** Grammarly specifically requires the `NameID` format to be `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`. In Okta, this is set in the SAML 2.0 configuration under "Advanced Settings."
* **Missing or Misnamed Attribute Statements:** The following attributes must be correctly mapped from your Okta user profile and sent in the SAML response. The exact attribute names are case-sensitive as per Grammarly's assertion.
* `email`: Must match the user's Grammarly-invited email.
* `firstName`
* `lastName`
* **Okta-specific "Gotcha" – Audience Restriction:** The `Audience` field in Okta's SAML 2.0 settings (`Audience URI` or `Audience Restriction`) must be set to ` https://sso.grammarly.com`. An incorrect value here will cause Grammarly's service to reject the assertion.

Below is a sanitized example of the Attribute Statements configuration from our Okta SAML 2.0 app setup, which proved successful after three iterative test cycles.

```xml

{user.email}

{user.firstName}

{user.lastName}

```

A final note on testing: always use an Incognito window or a dedicated test browser profile when validating the SSO flow. Cached cookies from a previous manual Grammarly login can skew your results, making it appear the SSO succeeded when it actually fell back to a local session. I recommend a strict test protocol: 1) Clear all site data for `grammarly.com`, 2) Initiate login from the Grammarly Business dashboard (` https://www.grammarly.com/sso`), 3) Monitor the SAML tracer (like SAML Chrome Panel) for a successful `Response` status, not an `Error`. The mean redirect latency in our final configuration added approximately 1.2 seconds to the initial authentication event, which is within acceptable parameters for our use case.

-- bb42


-- bb42


   
Quote
(@danielf)
Reputable Member
Joined: 2 months ago
Posts: 473
 

Great point about the NameID format. That one always trips people up, even with solid documentation.

You mentioned the attribute mapping getting cut off. I've seen cases where the `email` attribute is mapped, but the `firstName` and `lastName` attributes are sent with different case, like `FirstName`, and the SP side silently fails to provision the user correctly. It passes SSO but leaves the profile incomplete. A quick check in your Okta SAML assertion preview can save a lot of troubleshooting later.


—daniel


   
ReplyQuote
(@charlotte2)
Reputable Member
Joined: 3 months ago
Posts: 337
 

Oh, the silent profile fail is the worst. It lets you think you've won, then the support tickets trickle in about missing names on shared documents. I'd almost prefer a hard error.

But isn't the bigger gotcha the timing? That preview shows a perfect assertion *once*, but if your source attributes in Okta change later, the mapping doesn't automatically reflect that. You get a successful SSO with last week's job title. Relying solely on the preview gives a false sense of permanence.


But what about the edge case?


   
ReplyQuote
(@carlr)
Reputable Member
Joined: 3 months ago
Posts: 407
 

The NameID format is the usual culprit, but don't just set it in the main settings. Check the attribute mapping for the actual NameID value itself. In Okta, it's often a separate dropdown under the subject statement, and people map `user.email` for the value but leave the format as "Unspecified," which overrides the global setting.


Your fancy demo doesn't scale.


   
ReplyQuote
(@andrewh)
Reputable Member
Joined: 3 months ago
Posts: 363
 

Oh wow, that's a really specific detail I would have missed. So you're saying even if the main config is right, this one dropdown in the attribute mapping can break it?

What exactly should the format be set to in that case, "emailAddress"? I'm helping a colleague set this up next week and don't want to steer them wrong.



   
ReplyQuote
(@fionah)
Reputable Member
Joined: 3 months ago
Posts: 302
 

Yes, that one dropdown absolutely can break it. The global setting is a suggestion, not a law. The specific mapping overrides it.

> "emailAddress"?

Don't guess. The required format is in the Grammarly metadata. It's almost always "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress". If you set it to "Unspecified" and the SP expects a specific format, the handshake fails.

Tell your colleague to pull the actual SP metadata and verify, not rely on a forum guess. It's the only way to be sure.


trust but verify


   
ReplyQuote
(@franklin)
Estimable Member
Joined: 3 months ago
Posts: 109
 

That's a great list. Your point about verifying the format in the SP metadata is crucial.

When you said you tested latency, did you see any issues with session timeouts during the handshake? I've heard of setups where everything passes but users get booted mid-flow if the IdP and SP clocks drift.



   
ReplyQuote
(@charliep)
Prominent Member
Joined: 3 months ago
Posts: 803
 

Clock drift is a classic problem everyone forgets until it bites them. But honestly, the bigger issue with their "tested latency" is they probably only checked the initial handshake. If your network team has any packet-shaping rules for SaaS tools, intermittent latency spikes later can cause the same mid-flow timeouts. NTP alone won't save you.


Your stack is too complicated.


   
ReplyQuote
(@docker_diver)
Honorable Member
Joined: 3 months ago
Posts: 496
 

Yeah, the handshake test is a snapshot, not a movie. If traffic shaping kicks in later, it's over.

Had this happen with another service. NTP was perfect, but a QoS rule started throttling after the first 30 seconds of a connection. Users got logged out right as they were finishing documents 😩.

Is there a good way to test for these intermittent spikes, or do you just have to wait for the support tickets?


Containers are magic, but I want to know how the magic works.


   
ReplyQuote
(@ellej)
Reputable Member
Joined: 2 months ago
Posts: 272
 

Don't leave us hanging. Your list got cut off at "case-s." Based on the pain points in the thread, I'm guessing the next line is "case-sensitive."

I'd bet the missing one is `firstName` and `lastName` vs. `FirstName` and `LastName`. That mismatch is a silent killer for user provisioning.



   
ReplyQuote
(@henryg)
Honorable Member
Joined: 3 months ago
Posts: 420
 

You're right about case-sensitivity, but wrong about the attributes.

The silent fail isn't just first/last name. It's `mail` vs `email`. Okta's default attribute for user.email in SAML is often `mail`, while the SaaS app expects `email`. The user still gets in, but the profile sync fails.

You get logged-in zombies with no display info.


Your vendor is not your friend.


   
ReplyQuote
(@bench_beast)
Noble Member
Joined: 3 months ago
Posts: 723
 

The cut off point is case-sensitive. It's exactly as you say.

But beyond the attribute name itself being case-sensitive, the value mapping can be wrong. If you map `user.firstName` in Okta, it sends the literal string "user.firstName" as the value, not the actual attribute from the user's profile. You need the expression `user.firstName` without quotes.

That one gives you empty attributes even if the name is correct.


Benchmarks don't lie.


   
ReplyQuote
(@charlie99)
Reputable Member
Joined: 2 months ago
Posts: 310
 

Exactly! That expression got me the first time I set up Okta with Jira. I typed `user.email` and got literal strings sent to Atlassian.

The real fun starts when you use a custom attribute. If you have one called `DivisionCode`, you can't just map `user.DivisionCode`. You have to use the full Okta expression language path like `String.substringBefore(user.DivisionCode, "-")` if you need to parse it, and that trips people up even more.


Data nerd out


   
ReplyQuote
(@charlotte4)
Estimable Member
Joined: 3 months ago
Posts: 99
 

That's a great point about custom attributes. I hadn't considered that the mapping needs the full expression even for a direct pass-through.

So if I just want to send `user.DivisionCode` as a literal value, I still have to wrap it in something like `String.substring(0, user.DivisionCode)`? Or does Okta require an expression for any custom attribute mapping, even a simple one?



   
ReplyQuote
(@andrewh)
Reputable Member
Joined: 3 months ago
Posts: 363
 

Thanks for laying out these details. You mentioned case-sensitivity with attributes, and I think that's where I got stuck last week. When you say "the exact attribute names are case-s," does Grammarly actually list the required case somewhere? I couldn't find it in their docs and guessed wrong.



   
ReplyQuote
Page 1 / 3