An application can ask the user to the enter the URL of their datapod. for e.g. this would look like https://george.datapod.igrant.io
If the individual provides a URL that doesn't end with /profile/card#me
path then, append that to the provided URL.
Append /profile/card#me
to the pod URL and perform HTTP GET to obtain the WebID document.
curl -X GET https://george.datapod.igrant.io/profile/card\#me
Apply the regex pattern to get the solid OIDC URL from the response text.
solid:oidcIssuer\s+(<[^>]+>)
The above regex captures the OIDC URL and can be accessed from the captured group. Apply the same using the techniques available for the implementation language.
Append /.well-known/openid-configuration
to the OIDC issuer URL and perform HTTP GET to obtain the OpenID configuration in JSON format. A sample configuration is as below:
{
"issuer": "https://datapod.igrant.io",
"jwks_uri": "https://datapod.igrant.io/jwks",
"scopes_supported": [
"openid",
"offline_access"
],
"response_types_supported": [
"code",
"code token",
"code id_token",
"id_token code",
"id_token",
"id_token token",
"code id_token token",
"none"
],
"token_types_supported": [
"legacyPop",
"dpop"
],
"response_modes_supported": [
"query",
"fragment"
],
"grant_types_supported": [
"authorization_code",
"implicit",
"refresh_token",
"client_credentials"
],
"subject_types_supported": [
"public"
],
"id_token_signing_alg_values_supported": [
"RS256"
],
"token_endpoint_auth_methods_supported": [
"client_secret_basic"
],
"token_endpoint_auth_signing_alg_values_supported": [
"RS256"
],
"display_values_supported": [],
"claim_types_supported": [
"normal"
],
"claims_supported": [],
"claims_parameter_supported": false,
"request_parameter_supported": true,
"request_uri_parameter_supported": false,
"require_request_uri_registration": false,
"check_session_iframe": "https://datapod.igrant.io/session",
"end_session_endpoint": "https://datapod.igrant.io/logout",
"authorization_endpoint": "https://datapod.igrant.io/authorize",
"token_endpoint": "https://datapod.igrant.io/token",
"userinfo_endpoint": "https://datapod.igrant.io/userinfo",
"registration_endpoint": "https://datapod.igrant.io/register"
}
Store authorization_endpoint
and token_endpoint
from OpenID configuration for later use.
Append /register
to the OIDC issuer URL obtained earlier and HTTP POST with below request body to perform dynamic OpenID client registration.
{
"client_name": "<client_name>",
"application_type": "web",
"redirect_uris": "<array of redirect_uris>",
"subject_type": "public",
"token_endpoint_auth_method": "client_secret_basic",
"id_token_signed_response_alg": "RS256",
"grant_types": ['authorization_code', 'refresh_token']
}
client_name
: Can be random name. For e.g. Data Walletredirect_uris
: Must be an array of URLs. Upon successful login, the individual would be redirected to this URL. For Android application, this is preferred to be a dynamic link and for iOS this could be universal link. Upon redirection, this URL would also receive a query parameter -code
. This code must be then exchanged to obtain theaccess_token
andrefresh_token
pairs.
Save the client registration response JSON which would contain the client_id
required in the later stages.
Note: One possible optimisation that can be done in the application side would be to check the OIDC issuer URL, and don't register the client application more than once for the same URL.
- Generate a random 64 byte string. This is called as code verifier. This value need to be stored in the local storage as it is required to obtain the
access_token
andrefresh_token
in the later steps. - Now let's prepare code challenge using the code verifier.
- Encode the code verifier as ASCII.
- Take SHA256 digest of the code verifier.
- Base64 URL safe encode the digest and decode the it as ASCII.
- Strip the '=' characters at the end of the encoded string and this string becomes the code challenge.
Append the following query params to the authorization_endpoint
that was obtained in Step 1.3.
response_type
- The default value iscode
redirect_uri
- URL same as theredirect_uris
in Step 1.4, but as a string and not array.scope
- The default value isopenid webid offline_access
client_id
- The client ID obtained in Step 1.4code_challenge_method
- The default value isS256
code_challenge
- The code challenge created in Step 1.5.1
This authorization URL will take the Individual to Data Pod login page. For e.g. In Data Wallet app, a webview will open up and navigate the Individual to prepared authorization URL with query params.
On successful login, the login page will be redirected to the redirect_uri
configured.
Fetch the authorization code from the code
query param in the redirect_uri
.
- Generate a random uuid4 string. This is DPoP unique identifier.
- Generate elliptic curve P256 private key, public key pair with random key id (Random uuid4 string).
- Export the public key JWK. A sample is given below:
{
"kty": "EC",
"use": "sig",
"alg": "EC",
"kid": "abc",
"crv": "P-256",
"x": "avZfzz-fM7uL85jpxfUfYFBpWnX3RY_mt94bHL7RF9o",
"y": "llhm4mT2b7R9Enki8cei1DIcqMt1Y6XzXnMo0eWNy0A"
}
- Create a JWT token and sign it using the keypair created in the previous steps.
JWT header should be as given below:
{
"alg": "ES256",
"typ": "dpop+jwt",
"jwk": "<public key JWK>"
}
JWT claims should be as given below:
{
"htu": "<http_token_usage_url in this it is token_endpoint saved in Step 1.3>",
"htm": "<http_method in this case it is POST>",
"jti": "<dpop_unique_identifier>",
"iat": "epoch time in seconds"
}
Note: The public key and private key pair must be saved for further use.
Perform HTTP POST against the token endpoint. With following headers and request body:
Headers:
{
"DPoP": "<dpop_jwt from previous step>",
"content-type": "application/x-www-form-urlencoded"
}
Request body:
{
"grant_type": "authorization_code",
"code_verifier": "<code_verifier>",
"code": "<authorization_code>",
"client_id": "<client_id>",
"redirect_uri": "<redirect_uri>"
}
The response shall contain the access_token
and refresh_token
. This needs to persisted in local storage should be not be lost when Data Wallet app reopens.
Note: The access token should also be refreshed if it expires.
In order to refresh an access token, the request body should be:
{
"grant_type": "refresh_token",
"refresh_token": "<refresh_token>",
"client_secret": "<client_secret obtained in Step 1.4>",
"client_id": "<client_id>"
}
- Generate a random uuid4 string. This is DPoP unique identifier.
- Create a JWT token and sign it using the keypair created in the Step 1.6.1
JWT header should be as given below:
{
"alg": "ES256",
"typ": "dpop+jwt",
"jwk": "<public key JWK>"
}
JWT claims should be as given below:
{
"htu": "<http_token_usage_url>",
"htm": "PUT",
"jti": "<dpop_unique_identifier>",
"iat": "epoch time in seconds"
}
http_token_usage_url
is datapod_url
+ new folder path
for .e.g. https://george.datapod.igrant.io/DataWallet/Backups/
Remember to put slash character (/
) at the very end of the new folder path.
Perform HTTP PUT operation against http_token_usage_url
in the previous step, with following headers:
{
"accept": "text/turtle;q=1.0, */*;q=0.5",
"authorization": "DPoP <access_token>",
"dpop": "<dpop_jwt>",
"link": "<http://www.w3.org/ns/ldp#BasicContainer>; rel='type'"
}
- Generate a random uuid4 string. This is DPoP unique identifier.
- Create a JWT token and sign it using the keypair created in the Step 1.6.1
JWT header should be as given below:
{
"alg": "ES256",
"typ": "dpop+jwt",
"jwk": "<public key JWK>"
}
JWT claims should be as given below:
{
"htu": "<http_token_usage_url>",
"htm": "POST",
"jti": "<dpop_unique_identifier>",
"iat": "epoch time in seconds"
}
http_token_usage_url
is datapod_url
+ folder path to where this file uploaded
for .e.g. https://george.datapod.igrant.io/DataWallet/Backups/
Remember to put slash character (/
) at the very end of the folder path.
Perform HTTP POST operation against http_token_usage_url
in the previous step, with following headers:
{
"accept": "*/*",
"authorization": "DPoP <access_token>",
"dpop": "<dpop_jwt>",
"slug": "<file name>",
"content-type": "<mime_type_of_file for e.g. application/zip for zip file>"
}
The request should also contain the file content bytes to be uploaded. This depends on the implementation language and http client library how you can post a file to the server.
- Generate a random uuid4 string. This is DPoP unique identifier.
- Create a JWT token and sign it using the keypair created in the Step 1.6.1
JWT header should be as given below:
{
"alg": "ES256",
"typ": "dpop+jwt",
"jwk": "<public key JWK>"
}
JWT claims should be as given below:
{
"htu": "<http_token_usage_url>",
"htm": "GET",
"jti": "<dpop_unique_identifier>",
"iat": "epoch time in seconds"
}
http_token_usage_url
is datapod_url
+ file path
for .e.g. https://george.datapod.igrant.io/DataWallet/Backups/backup.zip
Remember to put slash character (/
) at the very end of the folder path.
Perform HTTP GET operation against http_token_usage_url
in the previous step, with following headers:
{
"accept": "text/turtle;q=1.0, */*;q=0.5",
"authorization": "DPoP <access_token>",
"dpop": "<dpop_jwt>"
}
The request will download the file to the local file system. This depends on the implementation language and http client library how you can download a file to local file system.
- Generate a random uuid4 string. This is DPoP unique identifier.
- Create a JWT token and sign it using the keypair created in the Step 1.6.1
JWT header should be as given below:
{
"alg": "ES256",
"typ": "dpop+jwt",
"jwk": "<public key JWK>"
}
JWT claims should be as given below:
{
"htu": "<http_token_usage_url>",
"htm": "GET",
"jti": "<dpop_unique_identifier>",
"iat": "epoch time in seconds"
}
http_token_usage_url
is datapod_url
+ file path
for .e.g. https://george.datapod.igrant.io/DataWallet/Backups/
Remember to put slash character (/
) at the very end of the folder path.
Perform HTTP GET operation against http_token_usage_url
in the previous step, with following headers:
{
"accept": "text/turtle;q=1.0, */*;q=0.5",
"authorization": "DPoP <access_token>",
"dpop": "<dpop_jwt>"
}
Apply the regex pattern to get all the resources in the folder
ldp:contains\s+(.*)[;\.]
The above regex captures the resources list and can be accessed from the captured group. Apply the same using the techniques available for the implementation language.
Iterate through the list and remove prefix, suffix angle brackets.