AMP Authentication

Feb. 14, 2017, 2:34 p.m.

I finally got the AMP forms working as expected. It was a bit tricky to figure out so I will outline the issues I encountered and how I solved them. The situation I was working with was making a comments form for the AMP version of my blog pages.

The first issue I had to deal with was that a user can't leave a comment unless they are logged in. In the rest of the app I use the session to determine if the user is logged in, but AMP has it's own protocol for doing that, which involves making AJAX requests to a page which returns a JSON response to determine if the user is logged in. In this case, in the controller I simply do an Auth::check() and return a JSON response depending on the results of the check(). 

The issues arose from the fact that AMP requires specific response headers, which took me a while to figure out how to set properly. I wasn't able to find much documentation on the values of these headers, but I was able to figure out the proper values.

The headers required were:

  • Access-Control-Allow-Credentials: true
  • Access-Control-Expose-Headers: AMP-Access-Control-Allow-Source-Origin
  • Access-Control-Allow-Origin
  • AMP-Access-Control-Allow-Source-Origin

The latter two headers need to have specific values, and although they ended up being the same in most cases, I set them to the separate values to make sure errors won't occur.

The value for Access-Control-Allow-Origin needs to be the "origin" header made in the request, which I get with:

$request->header('origin')

The value for the AMP-Access-Control-Allow-Source-Origin needs reflect the value passed in the URL to the request, which is a parameter named:  __amp_source_origin.

The authorization page can return a variety of values to indicate whether the user has a subscription, if they can view a specific number of free articles, and what they have access to. But in my case all I need to know is whether they are logged in or not, so I just return the JSON data:

{loggedIn: true}

To enable content being displayed differently based on authorization you need to include the following scripts:

<script async custom-element="amp-access" src="https://cdn.ampproject.org/v0/amp-access-0.1.js"></script>
<script async custom-element="amp-analytics" src="https://cdn.ampproject.org/v0/amp-analytics-0.1.js"></script>

You also need to include the following in a script to tell the scripts what to do and where to get the info from:
{
    "authorization": "[Auth URI]",
    "noPingback": "true",
    "login": {
        "sign-in": "[Login URI]",
        "sign-out": "[Logout URI]"
    },
    "authorizationFallbackResponse": {
        "error": true,
        "loggedIn": false
    }
}

Where [Auth URI] is the URI detailed above which returns whether the user is logged in or not; [login URI] is the URI to allow the user to login; and [logout URI] is the URI to allow the user to logout. All URIs must either be HTTPS or // or AMP will complain about them and won't function properly.

Then the following code is included in the template:

<span amp-access="NOT loggedIn" role="button" tabindex="0" amp-access-hide>
    <button on="tap:amp-access.login-sign-in" class="btn btn-xs btn-primary comment-button">Login</button>
    Please login to comment<br><BR>
</span>
<span amp-access="loggedIn">
    @include('amp._commentForm')
</span>

The amp-access attribute in the span tells the page NOT to display the section if the user is loggedIn - presumably you could vary this to reference other data returned by the auth page. The on attribute of the button tells the page to reference the login:sign-in attribute of the amp-access script when it is tapped, so it will launch the [login URI] when the button is clicked. And finally the amp-access="loggedIn" attribute says that if the user IS logged in the commentForm will be included.

For me the most complicated part was figuring out the response headers required and their values, once I got that figured out the rest worked pretty easily. The next step was getting the actual form to submit and update the page properly. I'll write about that in the next post.

Labels: coding , laravel , amp

No comments

Laravel Login Authentication and Redirect

Feb. 12, 2017, 12:08 p.m.

I was having a hard time with the Laravel auth package. If you use the out-of-the-box Laravel Auth, if you try to access a page you don't have access to the Auth will redirect you to the login page and then after a successful login redirect you back to the page you were trying to access.

This works fine. But if I go to a page and then click on login it would redirect me back to a page specified in the Auth controller instead of to the page I was on before I clicked login. I searched for a while and found some info, but not much addressing this specific issue. 

I finally found this thread on Laracasts which gives a simple and easy solution to the problem. 

The solution is to override the login form method in the LoginController.php in the app directory. I added this function:

public function showLoginForm(){
     if(!session()->has('url.intended')){
          session()->put('url.intended', url()->previous());
     }
     return view('auth.login');
}

This pushes the previous page onto the session as url.intended, which is the same thing the middleware does. But this does it in all cases, not just when the middleware catches an auth error. After login the Auth controllers now send you back to url.intended instead of to the default page specified in $redirectTo.

Labels: coding , laravel

No comments

Laravel Pagination and Ajax

Feb. 12, 2017, 12:08 p.m.

I just updated the search of my records here so it would load the results using Ajax instead of refreshing the whole page. Everything seemed to work fine, but then I noticed that it broke the Laravel pagination. I included the pagination in the section of the page reloaded by searches and sorts so it would update appropriately, but then the page links just loaded the results and didn't load the results into the div on the page where they were supposed to be.

This was a bit tricky to solve. What I ended up doing was exporting the pagination views to my resources directory, and then editing it there. To each of the pagination links I added two things:

  • class="page-link"
  • data-val="{ number of page to be loaded}"

Then I added a script to the page that triggers when you click an object with the class of page-link, that gets the page to be loaded from the data-val attribute and submits that to the script that loads the appropriate page with the appropriate variables. Then I did the same thing for the sort links - instead of having each trigger it's own script I made one script triggered by a click on the class and put the data in the data-val.

Labels: coding , laravel

No comments

Backing Up Data to Amazon S3

Feb. 12, 2017, 12:06 p.m.

I decided I wanted to backup my database somewhere other than my server. All of my code is in git so the only thing that could be lost in case of server errors is the database. To start I wrote a little shell script to dump the database using mysqldump. I wasn't sure where to put the SQL file to keep it off-server. My first thought was to put it in git, which was easy to do in the script. So I updated the script to add the file to git, commit the changes and push the repo up.

After a bit more thought I decided that might not be the best way to do it. It worked fine, but my usual workflow is I make all changes locally and then push it to git and then pull to production - I don't make any changes on the production server unless absolutely necessary. While adding files to git that don't exist in my dev environment shouldn't really cause any problems, I thought there must be a better way.

So I decided to put the dump file into an Amazon S3 bucket. Laravel can use S3 as a filesystem, as documented here, but I had tried to use this before and not had much luck. I saw that Amazon had a PHP package to interact with S3, which is SDKforPHP, so I thought I would try that out. After a little bit more digging I found that Amazon also has a package specifically for Laravel, which is located here. That turned out to be the winner. As opposed to trying to read pages of documentation for Laravel's file system or the Amazon SDK, all I need was a few lines of code and I was up and running. As a note, this package keeps the Amazon Keys and Secrets in the .env file, which is a lot better than keeping them in the filesystem.php config file like Laravel does. If you are going to use Laravel's S3 filesystem I suggest you update the filesystem.php file to pull them from the .env.

Now that I was able to upload files to S3 from a browser, the next step was to create an artisan command that I could add to my shell script. The Laravel documentation for this was clear and easy to follow. The only problem I had was a typo that for some reason didn't throw an error locally, but did on my production server. Other than that this is tested and working.

I had considered using S3 for this site in the past, but decided not to since I had problems with the Laravel S3 filesystem. Now that I've integrated with S3 so easily I may revisit that decision.

 

Labels: coding

No comments

Archives