crspybits / solidauthswift Goto Github PK
View Code? Open in Web Editor NEWSwift-based authentication for a Solid Pod
License: MIT License
Swift-based authentication for a Solid Pod
License: MIT License
Your public Solid POD URL will be:
https://crspybits.trinpod.us
Your public Solid WebID will be:
https://crspybits.trinpod.us/i
I used https://crspybits.trinpod.us as the issuer.
My logs show:
2021-09-05 18:47:02.926932-0600 SolidAuthSwiftDemo[47460:8895432] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed
2021-09-05T18:47:03-0600 debug : Received data: Optional("{\"issuer\":\"https:\\/\\/trinpod.us\",\"authorization_endpoint\":\"https:\\/\\/trinpod.us\\/authorize\",\"token_endpoint\":\"https:\\/\\/trinpod.us\\/token\",\"userinfo_endpoint\":\"https:\\/\\/trinpod.us\\/userinfo\",\"registration_endpoint\":\"https:\\/\\/trinpod.us\\/register\",\"end_session_endpoint\":\"https:\\/\\/trinpod.us\\/endSession\",\"jwks_uri\":\"https:\\/\\/trinpod.us\\/jwks\",\"response_types_supported\":[\"code\"],\"grant_types_supported\":[\"authorization_code\",\"refresh_token\"],\"subject_types_supported\":[\"public\"],\"claims_supported\":[\"sub\",\"webid\"],\"scopes_supported\":[\"openid\",\"profile\",\"email\"],\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\"],\"token_endpoint_auth_signing_alg_values_supported\":[\"RS256\"],\"request_object_signing_alg_values_supported\":[\"RS256\"],\"id_token_signing_alg_values_supported\":[\"RS256\"],\"code_challenge_methods_supported\":[\"plain\",\"S256\"],\"request_parameter_supported\":true,\"claims_parameter_supported\":\"false\",\"request_parameter_supported\":true,\"request_uri_parameter_supported\":true,\"require_request_uri_registration\":\"false\",\"response_modes_supported\":[\"query\",\"fragment\"]}")
2021-09-05T18:47:03-0600 debug : Received url response: <NSHTTPURLResponse: 0x6000036e10e0> { URL: https://crspybits.trinpod.us/.well-known/openid-configuration } { Status Code: 200, Headers {
"Access-Control-Allow-Credentials" = (
true
);
"Access-Control-Allow-Headers" = (
"Accept, Accept-Encoding, Accept-Language, Accept-Patch, Accept-Post, Access-Control-Allow-Credentials, Access-Control-Allow-Headers, Access-Control-Allow-Methods, Access-Control-Allow-Origin, Access-Control-Expose-Headers, Access-Control-Max-Age, Access-Control-Request-Headers, Access-Control-Request-Method, Allow, Authorization, Connection, Content-Length, Content-Security-Policy, Content-Type, Date, Dpop, ETag, Host, If-None-Match, Last-Modified, Link, Location, MS-Author-Via, Origin, Referer, Transfer-Encoding, Updates-Via, User, User-Agent, Vary, WAC-Allow, WWW-Authenticate, X-Content-Type-Options, X-Forwarded-For, X-Forwarded-Proto, X-Powered-By, X-Requested-With, cache-control, slug, hypergraph"
);
"Access-Control-Allow-Methods" = (
"OPTIONS, HEAD, GET, PATCH, POST, PUT, DELETE"
);
"Access-Control-Allow-Origin" = (
"*"
);
"Access-Control-Expose-Headers" = (
"Accept, Accept-Encoding, Accept-Language, Accept-Patch, Accept-Post, Access-Control-Allow-Credentials, Access-Control-Allow-Headers, Access-Control-Allow-Methods, Access-Control-Allow-Origin, Access-Control-Expose-Headers, Access-Control-Max-Age, Access-Control-Request-Headers, Access-Control-Request-Method, Allow, Authorization, Connection, Content-Length, Content-Security-Policy, Content-Type, Date, Dpop, ETag, Host, If-None-Match, Last-Modified, Link, Location, MS-Author-Via, Origin, Referer, Transfer-Encoding, Updates-Via, User, User-Agent, Vary, WAC-Allow, WWW-Authenticate, X-Content-Type-Options, X-Forwarded-For, X-Forwarded-Proto, X-Powered-By, X-Requested-With, cache-control, slug, hypergraph"
);
Allow = (
"OPTIONS, GET, HEAD, POST, PATCH, PUT, DELETE"
);
"Content-Type" = (
"application/json"
);
Date = (
"Mon, 06 Sep 2021 00:47:03 GMT"
);
"Transfer-Encoding" = (
Identity
);
Vary = (
"Accept, Authorization, Origin"
);
"X-Content-Type-Options" = (
nosniff
);
"X-Powered-By" = (
"TrinPod-Server/2.3.3"
);
} }
2021-09-05T18:47:03-0600 debug : JSONString: dict: [AnyHashable("client_name"): "Neebla", AnyHashable("token_endpoint_auth_method"): "client_secret_post", AnyHashable("redirect_uris"): ["biz.SpasticMuffin.Neebla.demo:/mypath"], AnyHashable("response_types"): "code id_token", AnyHashable("application_type"): "native", AnyHashable("grant_types"): ["authorization_code"]]
2021-09-05T18:47:03-0600 debug : postBody: 231 bytes
2021-09-05T18:47:03-0600 debug : Headers: Optional(["Content-Type": "application/json"])
2021-09-05T18:47:03-0600 debug : URL Request: https://trinpod.us/register
2021-09-05T18:47:03-0600 debug : Got registration response:
=============
OIDRegistrationResponse
clientID: Optional("8772AE25-3BF1-4E25-A465-B6FB5B4B62B3")
clientIDIssuedAt: nil
clientSecret: nil
clientSecretExpiresAt: nil
registrationAccessToken: nil
registrationClientURI: nil
additionalParameters: ["redirect_uris": <__NSSingleObjectArrayI 0x6000034d4090>(
biz.SpasticMuffin.Neebla.demo:/mypath
)
]
=============
2021-09-05T18:47:03-0600 debug : requestURL: https://trinpod.us/authorize?scope=openid%20offline_access%20profile%20webid&code_challenge=JFssOW1RIXHg7BvXuw60gYYB25tPynasE4IqYyh63xI&client_id=8772AE25-3BF1-4E25-A465-B6FB5B4B62B3&redirect_uri=biz.SpasticMuffin.Neebla.demo:/mypath&nonce=zGN8WCLLb1zbI074VVlddS9MYODXMAElj0uIha1VzOA&state=Q8fJQv4k4tdsEG2s6bOUxCx2_hnfid1VCrlS_t7THhY&response_type=code%20id_token&code_challenge_method=S256
SolidAuthSwift uses a public/private key pair to generate DPoP's (https://solid.github.io/solid-oidc/primer/). It seems like a bad idea to store these on a mobile client. They would presumably have to be static and built into the app. As I understand it, apps can be decompiled when phones are jail broken and this could break security. Thus, it seems like an iPhone app using SolidAuthSwift necessarily has to have a custom server if it wants to access resources on a Solid Server (e.g., files).
This happens to be the exactly use case I'm designing for for SyncServer/Neebla (e.g., https://github.com/SyncServerII/Neebla, https://github.com/SyncServerII/ServerMain), but not all iOS apps want to or need to use a custom server.
I'm trying to work out a general purpose mechanism (e.g., across Solid Pod providers) regarding what info needs to get sent to my custom server from my client mobile iOS app. The architecture of the system looks like this:
Client mobile iOS app
Custom server, used by that client iOS app.
Some collection of Solid issuers / Solid resource servers.
3.1. The client iOS app will connect to these for authentication. When a user signs into the Client mobile iOS app, they give their Solid issuer.
3.2. The custom server will use them for (a) validating id or access tokens, (b) refreshing access tokens, and (c) making resource requests to upload and download files. (In my case these are binary files such as images, text files, and movie files, not RDF's).
One aspect of this architecture that may be slightly different than others is that the mobile client uses a the Solid id token in order to authenticate with the custom server (this could be an access token too, I'm just planning on using an id token). This is a convenience for the mobile client. My system doesn't have its own sign in system; it uses the tokens of other sign systems for its own initial authentication. (The custom server does have its own accounts-- but the initial authentication gating access into the custom server are 3rd party tokens).
To accommodate the current variations in Solid issuers, I think what I need to do is:
On my mobile client after the /authorization endpoint request, in all cases I'll make a /token request which will give me a refresh token and ensure I have an id token. It seems that across current Solid issuers this can be done purely by authenticating with a client secret, so I will not have to have a public/private key pair on the mobile client.
I'll send the following to my custom server:
A. The id token: Need as mentioned above for initial authentication with my custom server.
B. The refresh token. Needed on the custom server for it to have updated access tokens.
C. The users storage IRI. Needed to make resource requests.
D. The client secret. Needed by some Solid issuers for making a /token request -- i.e., to update the refresh token.
E. A few other pieces of info needed to make /token requests or to verify id tokens (e.g., the URL for the endpoint to get the public keys to decode the id token).
I'm not getting as far in my code as using my webid I think, but my webid is: https://pod.inrupt.com/crspybits/profile/card#me
When I attempt to do the registration step, I get a response:
2021-09-05 16:10:15.496195-0600 SolidAuthSwiftDemo[46870:8846738] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed
2021-09-05T16:10:16-0600 debug : Received data: Optional("{\"authorization_endpoint\":\"https://broker.pod.inrupt.com/authorization\",\"claims_supported\":[\"sub\",\"webid\",\"iss\",\"aud\"],\"code_challenge_methods_supported\":[\"plain\",\"S256\"],\"dpop_signing_alg_values_supported\":[\"RS256\",\"ES256\"],\"end_session_endpoint\":\"https://broker.pod.inrupt.com/endsession\",\"grant_types_supported\":[\"authorization_code\",\"refresh_token\",\"client_credentials\"],\"id_token_signing_alg_values_supported\":[\"RS256\",\"ES256\"],\"issuer\":\"https://broker.pod.inrupt.com/\",\"jwks_uri\":\"https://broker.pod.inrupt.com/jwks\",\"registration_endpoint\":\"https://broker.pod.inrupt.com/registration\",\"response_types_supported\":[\"code\"],\"revocation_endpoint\":\"https://broker.pod.inrupt.com/revoke\",\"scopes_supported\":[\"openid\",\"offline_access\",\"profile\"],\"solid_oidc_supported\":\"https://solidproject.org/TR/solid-oidc\",\"subject_types_supported\":[\"public\"],\"token_endpoint\":\"https://broker.pod.inrupt.com/token\",\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\"],\"userinfo_endpoint\":\"https://broker.pod.inrupt.com/userinfo\"}")
2021-09-05T16:10:16-0600 debug : Received url response: <NSHTTPURLResponse: 0x600003015760> { URL: https://broker.pod.inrupt.com/.well-known/openid-configuration } { Status Code: 200, Headers {
"Content-Length" = (
1048
);
"Content-Type" = (
"application/json"
);
Date = (
"Sun, 05 Sep 2021 22:10:16 GMT"
);
"Strict-Transport-Security" = (
"max-age=15724800; includeSubDomains"
);
} }
2021-09-05T16:10:16-0600 debug : JSONString: dict: [AnyHashable("client_name"): "Neebla", AnyHashable("response_types"): "code id_token", AnyHashable("application_type"): "native", AnyHashable("grant_types"): ["authorization_code"], AnyHashable("token_endpoint_auth_method"): "client_secret_post", AnyHashable("redirect_uris"): ["biz.SpasticMuffin.Neebla.demo:/mypath"]]
2021-09-05T16:10:16-0600 debug : postBody: 231 bytes
2021-09-05T16:10:16-0600 debug : Headers: Optional(["Content-Type": "application/json"])
2021-09-05T16:10:16-0600 debug : URL Request: https://broker.pod.inrupt.com/registration
2021-09-05T16:10:16-0600 error : Failed client registration: oAuthError("Optional([\"error\": invalid_request, \"error_description\": 400 Bad Request])")
2021-09-05T16:10:16-0600 error : Sign In Controller failed: oAuthError("Optional([\"error\": invalid_request, \"error_description\": 400 Bad Request])")
Had a look at this. The code for Node Solid Server has in template:
Elsewhere app_origin is set:
app_origin: appOrigin
const appOrigin = appUrl.origin
I think they simply use the origin of the requesting url. They should use client_name!!
WebID registration is just in beta: "Solid Identity Provider support for WebID-based authentication for client applications is currently not widely available. Currently, only Inrupt’s Enterprise Solid Server (ESS) supports WebID-based authentication for client applications and only as a Beta feature." (https://docs.inrupt.com/developer-tools/javascript/client-libraries/tutorial/authenticate-client/)
(From https://gitter.im/solid/app-development)
Christopher Prince @crspybits Sep 09 20:50
So just so I'm clear, when it says in those docs "WebID-based authentication for client applications" that means static client registration?
@NSeydoux
Sep 10 03:10
Not quite: static client registration is vanilla OIDC (even OAuth 2.0) concept, where the app developer uses an out-of-band mechanism to get a client id/secret pair from a given Identity Provier, that the app can later re-use to be identified by the same Identity Provider. In this case, the identifiers is server-managed, and the developer manually provides client information when registering.
On the other hand, Solid Client identifiers (the spec is walking away from calling them WebID for technical reasons, but they're very much similar to a WebID for apps) is a Solid-OIDC-specific mechanism, where the client is in control of its identifier, which dereferences to a document where client information may be found by the identity provider. Details are provided in the draft spec: https://solid.github.io/solid-oidc/#clientids
In an open ecosystem such as Solid, where a given user is free to use the Identity Provider of their choice, regular static registration doesn't scale, which is why the alternative mechanism has been proposed.
I returned to doing some testing with the ESS, and got the following when my test attempted to do a /token request to refresh the access token:
root@0b7d9b20cb90:~/Apps/ServerSolidAccount# swift test --enable-test-discovery --filter ServerSolidAccountTests.GeneralTests/testDownloadExistingFile
[2/2] Merging module ServerSolidAccount
Test Suite 'Selected tests' started at 2021-09-24 00:30:29.626
Test Suite 'GeneralTests' started at 2021-09-24 00:30:29.644
Test Case 'GeneralTests.testDownloadExistingFile' started at 2021-09-24 00:30:29.644
Body: grant_type=refresh_token&refresh_token=VVwI1RtZGuQzSwqWeo2Srzwd1I80HMno&client_id=u2LAQ2cmfjsJAZOAgXP8MzKtmWopKAyx
request: Optional(https://broker.pod.inrupt.com/token)
request.allHTTPHeaderFields: Optional(["Authorization": "Basic dTJMQVEyY21manNKQVpPQWdYUDhNekt0bVdvcEtBeXg6NFJSSTU0b3JLQ2xzcjJMR2FEWnFqeEJ5MXFSWEZ3RjY=", "Dpop": "<snip>", "Content-Type": "application/x-www-form-urlencoded"])
String: Optional("{\"error\":\"invalid_client\",\"error_description\":\"Invalid client_id\"}")
<EXPR>:0: error: GeneralTests.testDownloadExistingFile : threw error "badStatusCode(401)"
Test Case 'GeneralTests.testDownloadExistingFile' failed (0.753 seconds)
Test Suite 'GeneralTests' failed at 2021-09-24 00:30:30.398
Executed 1 test, with 1 failure (1 unexpected) in 0.753 (0.753) seconds
Test Suite 'Selected tests' failed at 2021-09-24 00:30:30.398
Executed 1 test, with 1 failure (1 unexpected) in 0.753 (0.753) seconds
It looks like the registration for the client has expired.
Given that I'm trying to have a custom server that can run without the client having to re-register, I'm not sure what the fix is. Can the registration happen again on the server, without having to involve interaction with the user?
The last time I was doing these tests was on [2021-09-21T04:44:36.671Z]
-- and at that time the /token request refresh was working fine.
And if only the sub
claim is present that it always has the users webid?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.