|
| 1 | +--- |
| 2 | +title: "Sharing Credentials" |
| 3 | +version: "beta" |
| 4 | +--- |
| 5 | + |
| 6 | +## Audience |
| 7 | + |
| 8 | +This guide is dedicated to Vital customers looking to share the usage of their OAuth Client Credentials |
| 9 | +between Vital [Bring Your Own OAuth](/wearables/connecting-providers/bring-your-own-oauth/overview) and their |
| 10 | +existing production systems. |
| 11 | + |
| 12 | +This guide is not applicable if you: |
| 13 | + |
| 14 | +* do not have existing OAuth Client Credentials; or |
| 15 | +* can configure your OAuth Client Credentials to point exclusively at Vital. |
| 16 | + |
| 17 | + |
| 18 | +## Background |
| 19 | + |
| 20 | +Many providers use OAuth 2.0, in which each partner entity is registered as a confidential application. |
| 21 | +Each application is issued one set of OAuth client credentials (client ID + secret). |
| 22 | + |
| 23 | +As a Cross-Site Request Forgery (CSRF) mitigation, they also typically require every application to pre-register |
| 24 | +a Redirect URI. The `/authorize` flow would then validate inbound requests against the Redirect URI on record. |
| 25 | + |
| 26 | +Most providers allow exactly one Redirect URI registration per application, which means the Redirect URI |
| 27 | +must be one of: |
| 28 | + |
| 29 | +* your existing OAuth callback endpoint — under a domain you control; or |
| 30 | +* the Vital OAuth callback endpoint. |
| 31 | + |
| 32 | +It cannot be both. |
| 33 | + |
| 34 | +To avoid disruption to your production systems, we recommend sticking with your existing OAuth callback endpoint. |
| 35 | + |
| 36 | +<Info> |
| 37 | +Some providers, like Google Fit, also require domain verification. So it further restricts |
| 38 | +the Redirect URI to point at a domain you control. |
| 39 | +</Info> |
| 40 | + |
| 41 | +## Challenge |
| 42 | + |
| 43 | +Because of the Redirect URI restriction as described above, you must take some extra steps if you intend to |
| 44 | +share your OAuth Client Credentials between Vital [Bring Your Own OAuth](/wearables/connecting-providers/bring-your-own-oauth/overview) and your |
| 45 | +existing production systems. |
| 46 | + |
| 47 | + |
| 48 | +## Solution |
| 49 | + |
| 50 | +Vital recommends |
| 51 | + |
| 52 | +```mermaid |
| 53 | +sequenceDiagram |
| 54 | + participant Vital Link |
| 55 | + participant Your API |
| 56 | + participant BYOO Provider |
| 57 | + actor User Agent |
| 58 | + autonumber |
| 59 | + Vital Link-->>User Agent: OAuth 2.0 Authorization Request URL |
| 60 | + User Agent->>BYOO Provider: Navigate to URL (1) |
| 61 | + BYOO Provider-->>User Agent: Prompt |
| 62 | + User Agent-->>BYOO Provider: Approve or Deny |
| 63 | + BYOO Provider->>Your API: Redirect |
| 64 | + Your API->>Vital Link: Redirect |
| 65 | +``` |
| 66 | + |
| 67 | + |
| 68 | +<Steps> |
| 69 | + |
| 70 | +<Step title="Keep your current OAuth application settings"> |
| 71 | + |
| 72 | +The Redirect URI in your OAuth application settings should continue to point at your |
| 73 | +existing OAuth callback endpoint. |
| 74 | + |
| 75 | +</Step> |
| 76 | + |
| 77 | +<Step title="Extend your existing OAuth callback endpoint with Vital awareness"> |
| 78 | + |
| 79 | +Your OAuth callback endpoints can rely on the `state` query parameter to differentiate the request |
| 80 | +origin, i.e., whether it is from Vital or from your own production systems. |
| 81 | + |
| 82 | +OAuth requests originated from Vital would have the **Vital Link Token** as the `state` query parameter. |
| 83 | +Vital Link Token is a JSON Web Token (JWT), so you can use the _unverified claims_ of the JWT as a discriminator. |
| 84 | + |
| 85 | +When you detect a valid Vital Link Token, perform a `307 Temporary Redirect` |
| 86 | +to the [Vital OAuth callback endpoint](#vital-oauth-callback-endpoint-url) and passing on _all_ the URL query parameters. |
| 87 | + |
| 88 | +The exact JWT structure of the Vital Link Token is as follows: |
| 89 | + |
| 90 | + |
| 91 | +<AccordionGroup> |
| 92 | +<Accordion title="Encoding" defaultOpen> |
| 93 | +A JSON Web Token (JWT). |
| 94 | + |
| 95 | +<Warning> |
| 96 | +You need not verify the signature of this JWT. |
| 97 | + |
| 98 | +To prevent Cross-Site Request Forgery attacks, you must still check that the `aud` claim matches the |
| 99 | +expected [Vital Link API Base URL](#vital-link-api-base-url). |
| 100 | +</Warning> |
| 101 | + |
| 102 | +</Accordion> |
| 103 | +<Accordion title="Claim payload schema" defaultOpen> |
| 104 | +| Key | Value | |
| 105 | +| ----------------------- | ----------------------------------------------- | |
| 106 | +| `aud` | Vital Link API Base URL | |
| 107 | +| `sub` | Vital Link Session ID | |
| 108 | +| `team_id` | Vital Team ID | |
| 109 | +| `user_id` | Vital User ID | |
| 110 | +</Accordion> |
| 111 | +<Accordion title="Vital Link API Base URL" defaultOpen> |
| 112 | +| Environment | Base URL | |
| 113 | +| ----------------------- | -------- | |
| 114 | +| Production US | `https://api.tryvital.io/v2/link` |
| 115 | +| Production EU | `https://api.eu.tryvital.io/v2/link` |
| 116 | +| Sandbox US | `https://api.sandbox.tryvital.io/v2/link` |
| 117 | +| Sandbox EU | `https://api.sandbox.eu.tryvital.io/v2/link` |
| 118 | +</Accordion> |
| 119 | +<Accordion title="Vital OAuth callback endpoint URL" defaultOpen> |
| 120 | +| Environment | Base URL | |
| 121 | +| ----------------------- | -------- | |
| 122 | +| Production US | `https://api.tryvital.io/v2/link/connect/{PROVIDER}` |
| 123 | +| Production EU | `https://api.eu.tryvital.io/v2/link/connect/{PROVIDER}` |
| 124 | +| Sandbox US | `https://api.sandbox.tryvital.io/v2/link/connect/{PROVIDER}` |
| 125 | +| Sandbox EU | `https://api.sandbox.eu.tryvital.io/v2/link/connect/{PROVIDER}` |
| 126 | +</Accordion> |
| 127 | +<Accordion title="Example" defaultOpen> |
| 128 | +```python Python (FastAPI) |
| 129 | +import fastapi |
| 130 | +from urllib.parse import urlencode |
| 131 | + |
| 132 | +VITAL_LINK_BASE_URL = "https://api.tryvital.io/v2/link" |
| 133 | + |
| 134 | +@router.get("/oauth_callback/fitbit") |
| 135 | +def handle_oauth_callback( |
| 136 | + request: fastapi.Request, |
| 137 | + state: Annotated[str, fastapi.Query()], |
| 138 | + ..., |
| 139 | +) -> fastapi.Response: |
| 140 | + state_string = base64.b64decode(state) |
| 141 | + |
| 142 | + # Test if this is a JWT. |
| 143 | + # If so, try to extract the unverified claim payload, and see if this is |
| 144 | + # a Vital Link JWT. |
| 145 | + if ( |
| 146 | + state_string.startswith('{"') |
| 147 | + and (state_parts := state_string.split(".", maxsplit=3)) |
| 148 | + and len(state_parts) == 3 |
| 149 | + and (unverified_claims := json.loads(state_parts[1])) |
| 150 | + and unverified_claims.get("aud") == VITAL_LINK_BASE_URL |
| 151 | + ): |
| 152 | + query = urlencode(request.query_params) |
| 153 | + |
| 154 | + return fastapi.RedirectResponse(f"{VITAL_LINK_BASE_URL}/connect/fitbit?{query}") |
| 155 | + |
| 156 | + |
| 157 | + # Not a Vital Link JWT. Fallback to existing logic |
| 158 | + return process_oauth_callback(...) |
| 159 | + |
| 160 | +``` |
| 161 | +</Accordion> |
| 162 | +</AccordionGroup> |
| 163 | + |
| 164 | +</Step> |
| 165 | + |
| 166 | +<Step title="Register your OAuth callback endpoint with Vital"> |
| 167 | + |
| 168 | +When you set your OAuth Client Credential through the [Set Team Custom Credentials](api-reference/org-management/team-custom-credentials/upsert-team-custom-credentials) |
| 169 | +endpoint, you must specify a Redirect URI override that points at your your OAuth callback endpoint. |
| 170 | + |
| 171 | +When a Redirect URI override is present, Vital uses the override value you provided to initiate the OAuth authorization flow. |
| 172 | + |
| 173 | +</Step> |
| 174 | + |
| 175 | +</Steps> |
0 commit comments