Integrating Hellō
Now that you have registered your application at Hellō and added the button to your page, let's complete the integration.
- Connect the button click to creating a Request URL
- Create a Request URL
- Make the request by redirecting the user's browser to the Request URL
- Receive the response
- Validate the ID Token
At this point, you know which user you are interacting with, and have any of the claims you requested.
1. Respond to Button Click
The button to initiate registration / login is either charcoal (#303030) on white, or white on charcoal. Note that the Hellō logo ō
is a o
with a macron. You can use the ō
character if you have <meta charset="UTF-8">
in your page <head>
element (best practice for HTML documents). For reference, the UTF-8 encoding is0xC5 0x8D
and the HTML markup is ō
.
<button onclick="login(event)" class="hello-btn hello-btn-black-on-light"></button>
<script>
async function login(event){
event.target.classList.add('hello-btn-loader') // Show spinner
event.target.disabled = true // Disable button
const requestURL = await fetch() // Fetch the request URL from your backend
window.location.href = requestURL // See next step for creating request URL
}
</script>
2. Create Request URL
The request URL is https://wallet.hello.coop/authorize
and a query with the following parameters
Parameter | Description |
---|---|
client_id | The client_id for your app from console.hello.coop |
redirect_uri | One of the redirect_uri values you registered for your app |
scope | The openid scope and zero or more scopes listed at Hellō Claims |
nonce | A unique string that will be included in the signed ID Token. This links the ID Token to your request |
response_type | Set this to id_token . While Hellō supports the code flow to be compatible with legacy platforms, the id_token flow is simpler as it does not require implementing PKCE - RFC7636 |
response_mode (optional) | Either fragment or form_post . Defaults to fragment . This parameter tells Hellō how you would like to receive the response.See 4. Receive Response for details |
state (optional) | A value representing the state of your application that will be returned as a parameter in the response |
Here is the request used by the GreenfieldDemo app
(line feeds added for readability)
https://wallet.hello.coop/authorize
?client_id=3574f001-0874-4b20-bffd-8f3e37634274
&nonce=b957cea0-f159-4390-ba48-5c5d7e943ea4
&redirect_uri=https://greenfielddemo.com/
&response_mode=fragment
&response_type=id_token
&scope=name+nickname+email+picture+openid
There is no difference between a request to register the user, or log in the user. If the user has previously released the same requested scopes to your app, they will not be prompted to release it again. If you have changed with scopes you are requesting, or the profile_update
is provided, the user will be prompted to select what to release.
Hellō supports response_type=id_token
and response_type=code
.
You can let users update their profile at Hellō as well by setting the profile_update
scope which will prompt the user to decide what information to change.
3. Make Request
Cause the user's browser to load the request URL
you created in Step 2
. Here are some examples:
- Set
window.location.href
with JavaScript
window.location.href = "https://wallet.hello.coop/authorize?..."
- An
<a>
tag with anhref
to therequestURL
<a href="https://wallet.hello.coop/authorize?..." /> ... </a>
- HTTP 302 redirect from the server
HTTP/1.1 302 Found
Location: https://wallet.hello.coop/authorize?...
The user will then interact with Hellō, when finished, they will be redirected back to your application with either an ID Token, or an error response.
4. Receive Response
Your app will receive the response as either fragment query parameters to the provided redirect_uri
if response_mode=fragment
, or in application/x-www-form-urlencoded
format in an HTTP POST to the provided redirect_uri
if response_mode=form_post
. If the user approved the request, the response will contain an id_token
parameter, and a state
parameter if provided. See Request Errors for unsuccessful responses.
Fragment Example (response_mode=fragment
)
https://greenfielddemo.com/#id_token=eyJhbGciOiJSUzI1...rest_of_ID_Token
The following sample JavaScript will acquire the id_token
from the fragment
const params = new URLSearchParams(window.location.hash.substring(1))
const id_token = params.get('id_token') // eyJhbGciOiJSUzI1...rest_of_ID_Token
Form Post Example (response_mode=form_post
)
POST / HTTP/1.1
Host: greenfielddemo.com
Content-Type: application/x-www-form-urlencoded
Content-Length: XX
id_token=eyJhbGciOiJSUzI1...rest_of_ID_Token
Note that a fragment
response is limited to the maximum URL length supported by the user's browser. Using form_post
does not have that constraint, and a larger ID Token can be returned to your application.
An ID Token is a JSON Web Token (JWT) RFC 7519 that has claims per OpenID Connect §2.
In the following example of a raw ID Token:
- purple is the header that describes the JWT;
- yellow is the payload of the ID Token; and
- green is the signature of the JWT.
Example ID Token
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImJmZWQzOTBlLThkMmYtNDE3NC1iMTM2LTBhN2U1MmM5MWUxZSJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5oZWxsby5jb29wIiwiYXVkIjoiMzU3NGYwMDEtMDg3NC00YjIwLWJmZmQtOGYzZTM3NjM0Mjc0Iiwibm9uY2UiOiJiOTU3Y2VhMC1mMTU5LTQzOTAtYmE0OC01YzVkN2U5NDNlYTQiLCJqdGkiOiI4YWQxNjdkMS1kMTcwLTQ2YzktYjNjNi00N2RkYTczNWE0ZTMiLCJzdWIiOiJmOWUyMWYwZi05ZjBlLTQxYjAtYTU4Yi1jMmQ2M2JjYzdiNGYiLCJzY29wZSI6WyJuYW1lIiwibmlja25hbWUiLCJwaWN0dXJlIiwiZW1haWwiLCJvcGVuaWQiXSwibmFtZSI6IkRpY2sgSGFyZHQiLCJuaWNrbmFtZSI6IkRpY2siLCJwaWN0dXJlIjoiaHR0cHM6Ly9jZG4uaGVsbG8uY29vcC9pbWFnZXMvZGVmYXVsdC1waWN0dXJlLnBuZyIsImVtYWlsIjoiZGljay5oYXJkdEBoZWxsby5jb29wIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImlhdCI6MTY0NTY0MTI4NywiZXhwIjoxNjQ1NjQ0ODg3fQ.vppFPOM1kE6qs4s0DbWVGn80P0TOHmE4tmzg78RrJyz4732n5PH4aEgVIqQrKHkSYO8CptA1BhOBW1oRg8YrbWnJP2o8O__tLW8W1j8BzasW1td_Q_zuWqzz1XemqpLbPVKcS5MNZkYbJXLwXUAgmCOyiWgVlsXRV5D2bWhe-MesbmIaW-Rdnhf_WFuLBjNM0FO3HpdeHkJ4-wFuzGQhgyputw1-V9yeUWkyqt-9uW09fJCHN6oE3ATA0BA3uGWoFpPRaMb4JKxNdlQkR7OAkofIe_dCLnM9xR5_zDSdGA8j45ufGaIy1poqbq8PIg52thaWunpwuc8zo9-kiMYuZw
Decoded ID Token (Header & Payload)
{
"header": {
"alg": "RS256",
"typ": "JWT",
"kid": "bfed390e-8d2f-4174-b136-0a7e52c91e1e"
},
"payload": {
"iss": "https://issuer.hello.coop",
"aud": "3574f001-0874-4b20-bffd-8f3e37634274",
"nonce": "b957cea0-f159-4390-ba48-5c5d7e943ea4",
"jti": "8ad167d1-d170-46c9-b3c6-47dda735a4e3",
"sub": "f9e21f0f-9f0e-41b0-a58b-c2d63bcc7b4f",
"scope": [
"name",
"nickname",
"picture",
"email",
"openid"
],
"name": "Dick Hardt",
"nickname": "Dick",
"picture": "https://cdn.hello.coop/images/default-picture.png",
"email": "dick.hardt@hello.coop",
"email_verified": true,
"iat": 1669399110,
"exp": 1669399410
}
}
Payload Explanation
Claim | Description |
---|---|
iss | Issuer of ID Token. Will always be https://issuer.hello.coop |
aud | Audience of ID Token. Will be your client_id |
nonce | The nonce that you optionally included in your request |
jti | A unique identifier for this ID Token generated by Hellō |
sub | The subject of the ID Token. A unique identifier for the user. We recommend you use this to identify your users. See FAQ 10 for details. |
scope | The scopes returned by Hellō. See FAQ 11 for details. |
name | The user's full name or legal name. |
nickname | The user's preferred name, nickname, or username. |
picture | A user's profile picture URL. See FAQ 12 for details. |
email | The user's email address. |
email_verified | Indicates email was verified. Will always be true from Hellō |
iat | The time the ID Token was issued in Epoch time |
exp | The time the ID Token expires. Hellō sets the expiry to be 5 minutes (300 seconds) after iat |
Your application now has an ID Token for the user, but before using it, you need to ensure it is valid, and not an ID Token an attacker has passed to your application. The ID Token header and signature are part of the validation procedure.
5. Validate ID Token
You can validate the id_token
by:
- Sending it back to the Hellō introspection API; or
- Perform validation yourself per OpenID Connect 3.1.3.7
5.1 Introspection
Hellō provides an introspection API per RFC 7662 athttps://wallet.hello.coop/oauth/introspect
that will examine the token, ensure it was from Hellō, has not expired, and return the payload.
No authentication is required to call the introspection endpoint. You MUST pass your client_id
, and if you provided a nonce
in the request URL
, you MUST provide the nonce. The token
, client_id
, and optional nonce
are sent as JSON.
Example Introspection JSON for earlier ID Token
{
"token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImJmZWQzOTBlLThkMmYtNDE3NC1iMTM2LTBhN2U1MmM5MWUxZSJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5oZWxsby5jb29wIiwiYXVkIjoiMzU3NGYwMDEtMDg3NC00YjIwLWJmZmQtOGYzZTM3NjM0Mjc0Iiwibm9uY2UiOiJiOTU3Y2VhMC1mMTU5LTQzOTAtYmE0OC01YzVkN2U5NDNlYTQiLCJqdGkiOiI4YWQxNjdkMS1kMTcwLTQ2YzktYjNjNi00N2RkYTczNWE0ZTMiLCJzdWIiOiJmOWUyMWYwZi05ZjBlLTQxYjAtYTU4Yi1jMmQ2M2JjYzdiNGYiLCJzY29wZSI6WyJuYW1lIiwibmlja25hbWUiLCJwaWN0dXJlIiwiZW1haWwiLCJvcGVuaWQiXSwibmFtZSI6IkRpY2sgSGFyZHQiLCJuaWNrbmFtZSI6IkRpY2siLCJwaWN0dXJlIjoiaHR0cHM6Ly9jZG4uaGVsbG8uY29vcC9pbWFnZXMvZGVmYXVsdC1waWN0dXJlLnBuZyIsImVtYWlsIjoiZGljay5oYXJkdEBoZWxsby5jb29wIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImlhdCI6MTY0NTY0MTI4NywiZXhwIjoxNjQ1NjQ0ODg3fQ.vppFPOM1kE6qs4s0DbWVGn80P0TOHmE4tmzg78RrJyz4732n5PH4aEgVIqQrKHkSYO8CptA1BhOBW1oRg8YrbWnJP2o8O__tLW8W1j8BzasW1td_Q_zuWqzz1XemqpLbPVKcS5MNZkYbJXLwXUAgmCOyiWgVlsXRV5D2bWhe-MesbmIaW-Rdnhf_WFuLBjNM0FO3HpdeHkJ4-wFuzGQhgyputw1-V9yeUWkyqt-9uW09fJCHN6oE3ATA0BA3uGWoFpPRaMb4JKxNdlQkR7OAkofIe_dCLnM9xR5_zDSdGA8j45ufGaIy1poqbq8PIg52thaWunpwuc8zo9-kiMYuZw",
"client_id":"3574f001-0874-4b20-bffd-8f3e37634274",
"nonce":"b957cea0-f159-4390-ba48-5c5d7e943ea4"
}
Sample code to make API call
const id_token // the ID Token received
const client_id // your apps client_id
const nonce // the nonce sent in the request
const url = 'https://wallet.hello.coop/oauth/introspect'
const params = {
token: id_token,
client_id: client_id,
nonce: nonce
}
const options = {
method: 'POST',
mode: 'cors',
cache: 'no-cache',
headers: {'Content-type': 'application/x-www-form-urlencoded'},
body: new URLSearchParams(params).toString()
}
const response = await fetch(url, options)
const json = await response.json()
Response JSON
If successfully validated, you will receive the ID Token payload with active:true
to indicate it is an active token. If unsuccessful, you will receive an Introspection Error.
Sample Introspection Response
{
"iss": "https://issuer.hello.coop",
"aud": "3574f001-0874-4b20-bffd-8f3e37634274",
"nonce": "b957cea0-f159-4390-ba48-5c5d7e943ea4",
"jti": "8ad167d1-d170-46c9-b3c6-47dda735a4e3",
"sub": "f9e21f0f-9f0e-41b0-a58b-c2d63bcc7b4f",
"scope": [
"name",
"nickname",
"picture",
"email",
"openid"
],
"name": "Dick Hardt",
"nickname": "Dick",
"picture": "https://cdn.hello.coop/images/default-picture.png",
"email": "dick.hardt@hello.coop",
"email_verified": true,
"iat": 1669399110,
"exp": 1669399410,
"active": true
}
5.2 Self Validation
There are many OpenID Connect libraries that include ID Token validation. The OpenID Foundation maintains a list here. Getting security right is HARD. We recommend you use a proven library and NOT write your own validation. We include the information below for reference.
Signature Verification Keys
Hellō provides OpenId Provider configuration information per OpenID Connect Discovery at:
https://issuer.hello.coop/.well-known/openid-configuration
The jwks_uri
property in the configuration file contains the URI for a JSON file containing the public keys in JSON Web Key format (RFC 7517) for verifying the signature per step (6) above.
Signature Verification Steps
Following are details for each ID Token validation step per OpenID Connect 3.1.3.7
- N/A - The ID Token is not encrypted
- The
iss
value MUST behttps://issuer.hello.coop
- The
aud
value MUST be theclient_id
value provided in the request - N/A - The ID Token will not contain multiple audiences
- There will not be an
azp
claim - The ID Token is signed per JWS. The certificates are XXX
- The
alg
value will beRS256
- N/A - the
alg
is alwaysRS256
- The current time must be before
exp
. Note the time is seconds since the Epoch, not milliseconds. ID Tokens expire after one hour. - The
iat
may be used by the client if the one hour expiry is longer than is desirable by the client. - The
nonce
is included if provided in the request. - The
acr
Claim is not supported at this time. - The
auth_time
Claim is not supported at this time.