Server side security - interfaces
The authorisation proxy needs to support three calls. Generally, they will happen over HTTPS. The responses should NEVER be cached and it is the proxy's responsibility to ensure that the correct caching headers are returned to enforce this.
Sign In
Sign In validates a user from subscription information or other credentials. It will be called when a user explicitly logs in with the application. It should return a token that can be used to verify the user's subscription state (see Verify Subscription). This token will be cached and used until the user either explicitly logs out or the token is flagged as expired. This call is intended to validate that a user is recognised by your subscription system, and it should return the same information regardless of whether the user's subscription is active or lapsed.
Note that you can have which credentials you'd like in the POST data or the query string.
Request
We recommend doing an HTTPS POST. A sample post using email and password would look like:
POST /sign_in/ HTTP/1.1 Host: app.mysite.com Proxy-Connection: keep-alive Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded Content-Length: 39 Accept-Language: en-gb Accept: */* Connection: keep-alive User-Agent: PugpigApp/1380904190 CFNetwork/672.0.2 Darwin/14.0.0 password=1234567&email=test%test.com
A sample HTTP GET example using subscriber number:
https://app.mysite.com/sign_in/?subscriber=SUBSCRIBER_NUMBER
The Pugpig apps can also optionally include a Device specific parameter that can be used by the server to limit conncurrent access for too many devices.
Response
HTTP 200: <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <token>TOKEN</token> <!-- Other information for debug purposes -->
If the user credentials are not valid, the response should look like this:
HTTP 200: <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <error status="notrecognised" message="Credentials not recognised"/> <!-- Other information for debug purposes -->
The status attribute and optional message attribute can be used to pass information back to the client as to why the request failed.
Renew token
You may want to occasionally expire tokens. The renew endpoint provides a mechanism for doing so when used with verify subscription's 'stale' state,
Request
http://YOURSITE/renew_token/?token=OLD_TOKEN
Response
The response format is the same as for signing in.
Verify Subscription
This will be called on app startup, and may periodically be called if the app believes the user's subscription state has changed. The client will persist this state locally. If the client updates the state and the proxy returns a HTTP 2XX, the client will update its last known good state. If the proxy returns any other HTTP code (or fails to return at all) the client will maintain the last known good state.
Request
http://YOURSITE/verify_subscription/?token=TOKEN
Response
HTTP 200: <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <subscription state="active" message="You are currently a Gold subsubscriber"> <!-- If this is supplied, it means the user only has access to specific issues, otherwise the have access to all. An empty issues element means the user has access to nothing. --> <issues> <issue>com.test.issue123</issue> </issues> <!-- Optional per-user information. Our default client implementation makes these values available as name:value pairs (scheme:term); your application can use them for custom processing. --> <userinfo> <category scheme="http://schema.mydomain.com/user/name" term="Harry Smith"/> <category scheme="http://schema.pugpig.com/custom_analytics#type" term="gold"/> <category scheme="http://schema.pugpig.com/custom_analytics#22" term="lapsed"/> </userinfo> <!-- Other information for debug purposes --> </subscription>
State can be one of:
- active - user is an active subscriber.
- inactive - user's subscription has lapsed.
- suspended (deprecated - use inactive instead) - user's subscription has been temporarily suspended. This is treated exactly like inactive.
- unknown - could not retrieve information about this user because the token is nonsense. The user will be logged out.
- stale - the token being used has been flagged as expired. If possible, the client will attempt to refresh the token with the renew endpoint. If the token cannot renew, or there is no renew, the user will be logged out. If the renew endpoint exists but is not responding, the client should treat this like unavailable.
- unavailable - the backend system is not available, so the app will use the last known good state
Important: If you are using issue based subscription, an empty <issues /> element must be returned if the user does not have access to any issues. Omitting this element implies that a user has access to EVERYTHING. Remember that an inactive user could still have entitlements in an issue based system.
Edition credentials
The edition credentials call verifies that a user may download an edition, and issues credentials that can be used for the actual download. Ideally this call will always respond with a HTTP 200. If it doesn't, the client will assume that the download is not allowed. The user id and password contained in the credentials response will be used to download the actual content via HTTP Basic authentication, so they will be visible and easy to decode. As such, they should not include any unencrypted information. Your content server will be responsible for verifying that the provided username and password are valid. If the subscription server is down or returning errors, your module could choose to fail open and allow all users with a token to gain access.
Request
https://YOURSITE/edition_credentials/?token=TOKEN&product_id=EDITION_ID
Response
If the user is permitted to download the product:
HTTP 200: <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <credentials> <userid>USER-ID</userid> <password>PASSWORD</password> <!-- Optional headers to be sent for edition creds --> <header name="X-My-Auth>ASDFASDFASD</password> <!-- Other information for debug purposes --> </credentials>
If the received token is invalid:
HTTP 200: <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <credentials> <error status="notrecognised" message="Authentication details not recognised" /> <!-- Other information for debug purposes --> </credentials>
If the user is not permitted to download the product:
HTTP 200: <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <credentials> <error status="notentitled" message="You are not entitled to this edition" /> <!-- Other information for debug purposes --> </credentials>
Or if the user's subscription has expired:
HTTP 200: <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <credentials> <error status="expired" message="Your subscription has expired" /> <!-- Other information for debug purposes --> </credentials>
As shown in the various examples, the error status attribute and subscription state information can be used to pass information back to the client as to why the request failed. The client may try to renew your token or provide additional messaging based on the user's subscription state.
Generating and verifying credentials
You can return any credentials you like in response to a verification call. This can range from very insecure (e.g. a fixed username and password that your content server verifies) to relatively secure (e.g. a one-time random user and password generated and stored in a database, which the content server can verify against). We tend to use something in between that's fairly secure but also quite easy to verify independently.
First choose a random shared secret. This can be any random string, and will be unique and constant for your server. This secret is your key and you should make sure it isn't compromised.
Now, when there is a request for credentials, generate them using something like this (PHP-ish) code:
// assuming $editionId is set to the edition being requested // assuming $sharedSecret is set to your shared secret $salt = generate_random_number(); $password = sha1("$editionId:$salt:$sharedSecret");
Note: This is the same response format for all authentication providers, including an iTunes Receipt Validator.
This combination will be unique for every user and every edition because of the salt and difficult to guess because of the shared secret.
When the app tries to download the content and you need to check the authentication, do this:
// assume $edition_id has been extracted from the request path somehow // assume $sharedSecret is set to your shared secret if (isset($edition_id) && isset($_SERVER['PHP_AUTH_USER'])) { $salt = $_SERVER['PHP_AUTH_USER']; $password = sha1("$edition_id:$salt:$sharedSecret"); if ($password == $_SERVER['PHP_AUTH_PW']) { // edition is authorised. allow download and exit. } } // In order to avoid the Newsstand issues when creds are not supplied, we immediately 403 instead of sending a 401 // header('WWW-Authenticate: Basic realm="PugpigSubscription"'); header('Cache-Control: no-cache'); header('HTTP/1.0 403 Forbidden'); echo 'You are not authorized to view this page.';
Comments
0 comments
Please sign in to leave a comment.