Skip to content
This repository has been archived by the owner on Apr 13, 2022. It is now read-only.

Difficulty in implementing application flow #35

Open
SimonShapiro opened this issue Jul 11, 2019 · 10 comments
Open

Difficulty in implementing application flow #35

SimonShapiro opened this issue Jul 11, 2019 · 10 comments
Assignees
Labels

Comments

@SimonShapiro
Copy link

SimonShapiro commented Jul 11, 2019

I use solid-auth-client as the mechanism to authenticate users and the app to access their PODs. I understand that once authenticated solid-auth-client places an object that is used later for interacting with their PODs into local storage. When I interact with the POD from the client using solid-auth-client everything works ok in spite of the app being built in Python and compiled to Javascript via Skulpt.

I am trying to re-use the solid-auth-client object from the browser by passing it on to the server so that the server can independently access the POD. There are many obvious use cases for this pattern. For example, I log in to the app and ask it to access my POD every night and send watermarked thumbnails of all new photos to an aggregator.

My choice of using Python wherever possible may have inter-op issues with the object in local storage, but theoretically shouldn't. I have been trying to follow the pattern set out in the updated spec for Sending a Request.

The authentication details are retrieved from localStorage with a javascript function:

  function getSolidAuthClient () {
    let solid_auth_client = JSON.parse(window.localStorage.getItem("solid-auth-client"))
    console.log({"localStorage": solid_auth_client, type:
                typeof(solid_auth_client)})
  	return solid_auth_client
  }

When the authentication details are passed to the Python server from local storage, it is held in a variable called solid_auth_client. First I set up the pop_token as a json object:

  session = solid_auth_client["session"]
  pop_token = {
      "iss" : "https://solid-sparql.anvil.app",
      "aud" : "https://anvil1.inrupt.net", 
      "exp" : session["idClaims"]["exp"],
      "iat" : session["idClaims"]["iat"],
      "id_token" : session["authorization"]["id_token"],
      "token_type" : "pop"
    }

I then retrieve the private_key from solid_auth_client and convert it to PEM format:

  rehydrate = jwk.JWK.from_json(solid_auth_client["oidc.session.privateKey"])
  private_key = rehydrate.export_to_pem(private_key=True, password=None)

I use the PEM version of the key to sign the jwt of the pop_token:

  pop_token = jwt.encode(pop_token, private_key, algorithm="RS256")

Finally, I use the pop_token as an authorization header in the http GET from the PODs private area.

  try:
    resp = anvil.http.request(
      method = "GET",
      headers={
        "authorization": "Bearer "+pop_token.decode("utf-8") 
      },
      url="https://anvil1.inrupt.net/private/test.ttl" 
    )
  except Exception as e:
    print("HTTP error", e)

This results in HTTP error 401.

@RubenVerborgh
Copy link

So, let's take a step back to see what it is that you really want to do.

It seem to me that you want a server to be able to perform authenticated resource access, right?

Down the road, this will be addressed with OAuth2 application tokens. But this has not been implemented yet.

In the meantime, you can use this hack: https://github.com/jeff-zucker/solid-file-client


I am trying to re-use the solid-auth-client object from the browser by passing it on to the server

Security measures (token expiration) will purposely prevent you from doing that.

@zenomt
Copy link

zenomt commented Jul 11, 2019

Down the road, this will be addressed with OAuth2 application tokens.

can you elaborate or link to what you mean here? i'm not sure what an "OAuth2 application token" is. are you talking about offline refresh tokens, "service accounts", some kind of privileged meta-account that can act as another/any user at that OP, ...?

@RubenVerborgh
Copy link

@zenomt
Copy link

zenomt commented Jul 11, 2019

@RubenVerborgh sorry, i still don't understand. assuming you're referring to the "Application-only authentication" mode on that page (since the "Application-user authentication" case is substantially the same as OIDC), for the decentralized Solid use case, wouldn't "app-only" access just be implemented by the app having a webid (that is, it's a bot), and granting access to the app's webid in ACLs? that also works for acl:AuthenticatedAgent cases, where you want some access token to access a substantially-public resource/API (perhaps for rate-limiting by webid). this (bots have webids) can be done today already, and could be done with less friction with #22. :)

also the "app-only" case on that page doesn't address the "app wants to act as the user even if the user is offline" situation, which i believe is the use case @SimonShapiro is talking about.

@RubenVerborgh
Copy link

To be honest, I'm not (trying to be) an expert in this matter; I'm merely echoing the high-level plan that I've heard.

Bots with WebIDs already sound good, but we haven't figured out the auth details for them (currently it's username/password with cookies).

But even if a bot has its own WebID, it would still be good if they could use something like an app token and secret, such that they act on behalf of a user.

So application-user authentication is the way (or something along those lines).

@zenomt
Copy link

zenomt commented Jul 11, 2019

Bots with WebIDs already sound good, but we haven't figured out the auth details for them (currently it's username/password with cookies).

all you need is for the app/bot's webid to list a solid:oidcIssuer URI that has a <URI>/.well-known/openid-configuration that answers with Content-type application/json, that has a jwks_uri entry that, when dereferenced, answers (with content-type application/json) a JWKS with a public key that your bot signs its own id_tokens with. the bot doesn't need to authenticate with its (potentially imaginary) OP because it just signs its own id_tokens (and POPTokens) without ever talking to an OP.

that should work today.

@RubenVerborgh
Copy link

Good point, indeed. Should work, just needs implementation.

@zenomt
Copy link

zenomt commented Jul 12, 2019

it looks like NSS (at least running on solid.community) only does an OPTIONS on the webid URI (presumably looking for the rel="http://openid.net/specs/connect/1.0/issuer" link), and does not actually retrieve the full profile document to look for the ?webid solid:oidcIssuer ?issuer triple.

this looks like an NSS bug. it will probably affect anyone whose webid document is hosted somewhere that didn't answer the expected Link to an OPTIONS (like me). specifically i saw this:

HTTP/1.1 401 Could not verify Web ID from token claims

and saw only this in my web server logs (duplicate fetches removed):

165.227.231.225 - - [11/Jul/2019:18:45:24 -0700] "GET /bot/oidc//.well-known/openid-configuration HTTP/1.1" 200 167 "-" "node-fetch/1.0 (+https://github.com/bitinn/node-fetch)" "zenomt.zenomt.com:443" 0.000 "-"
165.227.231.225 - - [11/Jul/2019:18:45:25 -0700] "GET /bot/oidc/jwks.json HTTP/1.1" 200 427 "-" "node-fetch/1.0 (+https://github.com/bitinn/node-fetch)" "zenomt.zenomt.com:443" 0.000 "-"
165.227.231.225 - - [11/Jul/2019:18:45:25 -0700] "OPTIONS /bot/card.ttl HTTP/1.1" 204 0 "-" "node-fetch/1.0 (+https://github.com/bitinn/node-fetch)" "zenomt.zenomt.com:443" 0.000 "-"

when trying to use my "bot" https://zenomt.zenomt.com/bot/card.ttl#me

and for the curious, here is the (expired) bearer token used with the above exchange:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJub25jZSI6ImQyM2Y5ZmQ3LWYxYmItNGIzYi1hNjc2LTEzNjY2NjgxMmU4YSIsInRva2VuX3R5cGUiOiJwb3AiLCJqdGkiOiIzNjIwZGYzMi01NTM2LTRlMmQtODU5MS0zZjBhNmE0NjM0ZjkiLCJhdWQiOiJodHRwczovL3plbm9tdC5zb2xpZC5jb21tdW5pdHkiLCJleHAiOjE1NjI4OTYwNDMsImlhdCI6MTU2Mjg5NTkyMywiaXNzIjoiaHR0cHM6Ly9hcHAuZXhhbXBsZS9vYXV0aC9jb2RlIiwiaWRfdG9rZW4iOiJleUpoYkdjaU9pSlNVekkxTmlJc0luUjVjQ0k2SWtwWFZDSXNJbXRwWkNJNklrc2lmUS5leUp1YjI1alpTSTZJakJtWkRZME9ETTRMV05qWlRFdE5ESTJPUzFoTmpOaExXWTBaRGRtWkdNeE9XWTJZaUlzSW5kbFltbGtJam9pYUhSMGNITTZMeTk2Wlc1dmJYUXVlbVZ1YjIxMExtTnZiUzlpYjNRdlkyRnlaQzUwZEd3amJXVWlMQ0poZFdRaU9sc2lZMnhwTFhSdmIyd2lMQ0pvZEhSd2N6b3ZMMkZ3Y0M1bGVHRnRjR3hsTDI5aGRYUm9MMk52WkdVaVhTd2ljM1ZpSWpvaWFIUjBjSE02THk5NlpXNXZiWFF1ZW1WdWIyMTBMbU52YlM5aWIzUXZZMkZ5WkM1MGRHd2piV1VpTENKcGMzTWlPaUpvZEhSd2N6b3ZMM3BsYm05dGRDNTZaVzV2YlhRdVkyOXRMMkp2ZEM5dmFXUmpMeUlzSW1OdVppSTZleUpxZDJzaU9uc2lhMlY1WDI5d2N5STZXeUoyWlhKcFpua2lYU3dpWlNJNklrRlJRVUlpTENKcmRIa2lPaUpTVTBFaUxDSmhiR2NpT2lKU1V6STFOaUlzSW00aU9pSTJMVEYxZFVwaVYyUXhjV05SWW5jeVYydFNhVGRvYm5wb1MyNDVkekp2Y1VSNVVEWlRXSFpOTjFkeU5tNUZSblU0UkRNd1kwMUdhbFZGVEY5M1kybEJTWFIxVWpKUVJWQlVTblJsZG5GWWEwTmhRbFI2TFRVMFVYUnlTekYzVWxScU4ySTVTM041WkRSWk4wUnZiRVJ3TVZVeFJtbEtObU16Y1hScGExVnhhbUppVTFReU1rb3djSGhMWWt4U2RHOVhSWFZ5V0VkWWJUVlRZVkZFYlMxSldsUnllRGROWmpSNlZXVTBWWFJqU2pORVRGbFZVVEJ4Vm1KbFFXbzRTV3MwTTBWM1lYVm1VblZIV0hOcFNqYzVSVlpQVFV0bVpUUlhVVTF3VFhsNExXUmtTemhqUzA0eFlqVjJORlJQZW5CWWVERjNOVmhaU2pVNFVsODBkR3RMWlRNNWVVeENjekY2VEZKeE4weE5SSEZ5YjBSNWRXaElTV2R6Y1ROd2JtNUNYMWxDWjJSeFFXVTNjR1pCYkZjNVR6aGFWWGRsVW1KRmJWOVNZVjkyWjNsNk1VNVBORWRVVmxOWk5rNHRjblJHZGtkVlYxRWlMQ0pyYVdRaU9pSkxJbjE5TENKaFkzSWlPaUl3SWl3aWFuUnBJam9pWkROaU5HVXhOR1l0T0dZMFpTMDBNalk1TFdFM05qY3RORE0wTkdFeVpESXdZelJqSWl3aVpYaHdJam94TlRZeU9EazVOVEl6TENKaGRYUm9YM1JwYldVaU9qRTFOakk0T1RVNU1qTXNJbUY2Y0NJNkltTnNhUzEwYjI5c0lpd2lhV0YwSWpveE5UWXlPRGsxT1RJemZRLnpfX2g1SC1RTlEzNWZSc0NLV2lLOG9nS2FIbXUyeWhsLTFXaW1pQkFSMjRvSnEwcFFVdG56TDZrbEZCNVhlRTlMMjZ6U0hSQnFaNDdUY1N3blZZSlRhR0pJNXJWRkhfdS1DVEgwbmFqUnNSdXB3ajBFS3hXUzM0U0d4NldpaHF4d0NkQXM3VlVoblgwUTdraTFVSzBESTFyMS1PR2VrMXZWbmt0WTNtekNPVlNJMXBUVkZNNVE1Vy1kVDN6bkdYUkFEWm5ObkpjcTFWR3NsUWF0SUZMX0ZvdnAwZlBDZnN4RnhYQ3NCZUU2LUNzM1NSU1NxMzB3V0RIY3ZNQXFzVXo4Q2JQVTFST1VCVlFJa2xxMlpqZnVZV2pvZzIxZ2REU1BhRkNYRWVzd2hoUVdTZG1Ud1c1MTV0NTA1TFNoZjRma19mcEtNRjNpTGNLaWZkVjVlM3RSZyJ9.W4y8flAWh6bqSDnDcM7HZUztzma66j2IJeQNzKWSBi7auH7PG85_x-aLNk6kYX-X-GIZG26iBIa-yP7GiWvwrAEm--hYzWTpRl6e0NGG3yFA42LbR-HzaXIenv_FKbh7YoLujoltDA1lYje8PJZvH5DRfga8MVLMiVBSwNJ5iY7DrNUZmOVTg6Cfr-HPWksxCJvT6TeTihxhK6u1cqBV2WiY7YSo9__YAsjN9O42bsBeN-MMqJU0J7OOdMyOueoR1H_rjbNGYg5-wkKMEYDTIkIkNCbHKPlgONzcmFhzqs1dO9eKNbA140NtMXrM9rL_dqQMzwcpL7_sVxaeGF2IlQ

this is the tool i whipped up to generate the above token.

luckily it looks like NSS didn't mind that the .well-known/openid-configuration was application/octet-stream instead of application/json. :) nginx doesn't have a way to set a content-type on a file without an extension (without setting the default content-type for everything in a directory).

i plan to do some more checking before i open a bug against NSS, to make sure i'm not doing something wrong.

@zenomt
Copy link

zenomt commented Jul 12, 2019

from source code inspection, i've found at least one bug in NSS (oidc-auth-manager) and one misfeature.

bug: oidc-auth-manager https://github.com/solid/oidc-auth-manager/blob/5827449e0d1d287668fff667661003e529eb8f9d/src/preferred-provider.js#L70-L73 drops any path part of an OIDC issuer URI, so it can never successfully match an id_token's iss if that claim has a path part (as my regular webid does, as well as the bot example above). issuers are allowed to have path parts, see (among other references) https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest .

misfeature: only one solid:oidcIssuer is properly supported. if a profile lists more than one, the results are nondeterministic: https://github.com/solid/oidc-auth-manager/blob/5827449e0d1d287668fff667661003e529eb8f9d/src/preferred-provider.js#L105 . i had hoped this wasn't the case at #30 (comment) .

i'll open a bug and enhancement for these two against oidc-auth-manager this weekend.

just from code inspection i still don't know why it won't load my profile document after doing an OPTIONS on it.

@zenomt
Copy link

zenomt commented Jul 13, 2019

i plan to do some more checking before i open a bug against NSS, to make sure i'm not doing something wrong.

the tool i wrote can generate a usable access token if the issuer URI has no path part.

as another annoyance: NSS seems to cache some representations (like for the openid-configuration and jwks_uri) for way too long, and appears to ignore the Cache-control header. i have "Cache-control: max-age=60" on responses for those but NSS doesn't seem to reload them even after a day+.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

3 participants