< Return to Video

RailsConf 2014 - Service Oriented Authenication by Jeremy Green

  • 0:18 - 0:18
    JEREMY GREEN: All right. So what I'm talking
  • 0:18 - 0:20
    about today is service oriented authentication.
  • 0:20 - 0:23
    And that's how you authenticate who a user
    is
  • 0:23 - 0:25
    when you want to start doing SOA and you need
  • 0:25 - 0:28
    to be able to authenticate the same person
  • 0:28 - 0:30
    from service to service.
  • 0:30 - 0:33
    I nearly went with this as a title slide,
  • 0:33 - 0:34
    but decided that I'd go with the other one
  • 0:34 - 0:36
    so that everybody knew that they're in the
    right
  • 0:36 - 0:40
    place. And then also because, I think this
    picture
  • 0:40 - 0:43
    is a, is a good visual metaphor for what
  • 0:43 - 0:46
    happens with our system sometimes.
  • 0:46 - 0:48
    We start with a nice little shiny feature
    that
  • 0:48 - 0:50
    is gonna be the thing that's gonna be unique
  • 0:50 - 0:52
    to our app, and then we start tacking on
  • 0:52 - 0:55
    more and more stuff and building it out and
  • 0:55 - 0:56
    some of the things that we build up are
  • 0:56 - 1:00
    very well-architected and well-constructed.
    Other bits of it are
  • 1:00 - 1:02
    these little organic things that kind of grow
    off
  • 1:02 - 1:06
    in weird ways that we don't quite understand
    exactly
  • 1:06 - 1:08
    why the ended up that way, but it works.
  • 1:08 - 1:10
    So we'll just let it be that. And just
  • 1:10 - 1:11
    keep going.
  • 1:11 - 1:16
    And, much like planets form by gravity attracting
    matter
  • 1:16 - 1:19
    into a sphere, I think that the gravitational
    pull
  • 1:19 - 1:24
    of login systems often causes monoliths to
    form. If
  • 1:24 - 1:26
    you were here a couple of sessions ago when
  • 1:26 - 1:29
    Stephen was talking about those ginormous
    user classes, you
  • 1:29 - 1:33
    know, that happens because you get your login
    system
  • 1:33 - 1:36
    started. Your users can log in and you want
  • 1:36 - 1:37
    them to own some stuff.
  • 1:37 - 1:40
    And so it's just natural that every new feature
  • 1:40 - 1:43
    that comes along gets tacked on to where the
  • 1:43 - 1:45
    users can log in. And so if you can
  • 1:45 - 1:47
    figure out how to get out of that cycle,
  • 1:47 - 1:50
    it makes it a lot easier to start dividing
  • 1:50 - 1:53
    your systems and your features up into vertical
    slices
  • 1:53 - 1:56
    of functionality.
  • 1:56 - 1:58
    So I, I have a subtitle for this, which
  • 1:58 - 2:03
    is Stumbling towards SOA, The story of CloudHDR.
    And
  • 2:03 - 2:04
    the reason I like this as a subtitle is
  • 2:04 - 2:08
    because SOA is not a spec. You can't go
  • 2:08 - 2:10
    read the RFC for it and just implement it
  • 2:10 - 2:13
    and be done. SOA is a, a way of
  • 2:13 - 2:15
    thinking about how you architect your applications
    and how
  • 2:15 - 2:19
    you design systems to interact with one another.
  • 2:19 - 2:22
    So, what I can tell you is what I've
  • 2:22 - 2:25
    done and what's worked for me. If these things
  • 2:25 - 2:27
    will work for you or not is gonna be
  • 2:27 - 2:29
    up to you to decide.
  • 2:29 - 2:32
    So, my original app was something that looked
    kind
  • 2:32 - 2:35
    of like this. It was a big monolithic app.
  • 2:35 - 2:38
    It started out with just the core of some
  • 2:38 - 2:42
    HDR photo-processing stuff, and then I kept
    tacking on
  • 2:42 - 2:44
    more and more stuff, till I had this huge
  • 2:44 - 2:46
    app that was slow to run, slow to deploy,
  • 2:46 - 2:49
    slow to test. And really just not much fun
  • 2:49 - 2:51
    to work with, as a developer, because I spent
  • 2:51 - 2:54
    more time waiting on the computer than I did
  • 2:54 - 2:56
    doing what I wanted to be doing.
  • 2:56 - 2:59
    So, I had a chance recently to totally rewrite
  • 2:59 - 3:01
    it, and I came up with a system that
  • 3:01 - 3:04
    looks more like this. It's a collection of
    services
  • 3:04 - 3:07
    that all talk to each other, only when they
  • 3:07 - 3:09
    need to. Each one of these things can be
  • 3:09 - 3:14
    deployed independently. Each deploy is very
    quick. Each app
  • 3:14 - 3:18
    is very quick to run, to test, to boot.
  • 3:18 - 3:19
    And I'm much happier with this now that I've
  • 3:19 - 3:20
    done it.
  • 3:20 - 3:22
    And the thing that allowed me to get there
  • 3:22 - 3:25
    was getting over the hurdle of, how do I,
  • 3:25 - 3:27
    what do I do with users? Once they've logged
  • 3:27 - 3:29
    in, how do I handle making it be the
  • 3:29 - 3:32
    same user as they go from app to app?
  • 3:32 - 3:33
    And I wanted something kind of like you have
  • 3:33 - 3:35
    at Amazon or Google. You know, most of those
  • 3:35 - 3:39
    places, they have dozens of web properties
    that you
  • 3:39 - 3:41
    can log into. But for every one of them,
  • 3:41 - 3:43
    any time you log in, you end up at
  • 3:43 - 3:45
    the same log in page. And then you get
  • 3:45 - 3:47
    sent back to wherever you were trying to go.
  • 3:47 - 3:50
    You know, you, same login page if you're gonna
  • 3:50 - 3:52
    buy something from Amazon on their store.
    If you're
  • 3:52 - 3:55
    trying to log in to AWS. Any of those
  • 3:55 - 3:59
    things. So I wanted to try to replicate that.
  • 3:59 - 4:01
    So what I turned to was OAuth2, and this
  • 4:01 - 4:05
    is a, an open standard for delegating authorization
    across
  • 4:05 - 4:08
    apps. And I've been really pretty happy with
    what
  • 4:08 - 4:10
    I came up with.
  • 4:10 - 4:12
    So, I know that you're probably thinking,
    well, this
  • 4:12 - 4:14
    is great if you can do a total rewrite,
  • 4:14 - 4:16
    or if you're starting with a fresh app. But,
  • 4:16 - 4:20
    what do I do with my monolith? And that's
  • 4:20 - 4:23
    OK. We can take baby steps to get to
  • 4:23 - 4:26
    a better place. So what we're gonna look at
  • 4:26 - 4:28
    is a way that you can start where you
  • 4:28 - 4:31
    are, even if where you are today looks like
  • 4:31 - 4:32
    that. A monolith.
  • 4:32 - 4:37
    You can plug on OAuth2 provider functionality
    into your
  • 4:37 - 4:41
    monolith to make it an app that can delay,
  • 4:41 - 4:44
    or that, can have authentication needs delegated
    to it,
  • 4:44 - 4:47
    and then you can start plugging in other services
  • 4:47 - 4:50
    that work alongside that app.
  • 4:50 - 4:53
    So, as a way to talk about this, I
  • 4:53 - 4:55
    thought we could call it the Mc-SOA. And that
  • 4:55 - 5:01
    is the Monolithic Centric Service Oriented
    Architecture. And I'm,
  • 5:01 - 5:05
    of course, not affiliated with McDonald's
    in any way.
  • 5:05 - 5:08
    So there's really two main portions to this
    talk.
  • 5:08 - 5:11
    One is an introduction to OAuth and how it
  • 5:11 - 5:13
    works and what it does. And then the second
  • 5:13 - 5:16
    part is, how do you implement that? And, of
  • 5:16 - 5:19
    course, this wouldn't be a valid tech talk
    if
  • 5:19 - 5:22
    we didn't start counting at one, or at zero,
  • 5:22 - 5:24
    I mean. And it, it's not very helpful if
  • 5:24 - 5:27
    you don't understand the context and the goals
    and
  • 5:27 - 5:29
    the requirements that lead me to make the
    decisions
  • 5:29 - 5:30
    that I made.
  • 5:30 - 5:33
    So, the first bit of context is just a
  • 5:33 - 5:35
    little bit of information about me. My name
    is
  • 5:35 - 5:37
    Jeremy Green. There's a list of some things
    that
  • 5:37 - 5:41
    I'm into in chronological order, by the way.
    I'm
  • 5:41 - 5:45
    one of the organizers of the OKcRuby group.
    That
  • 5:45 - 5:47
    group helped me a lot in getting this talk
  • 5:47 - 5:48
    ready. They've let me preview it for them
    and
  • 5:48 - 5:50
    gave me a lot of feedback. So thanks to
  • 5:50 - 5:52
    all of those people.
  • 5:52 - 5:53
    And then there are some ways you can contact
  • 5:53 - 5:56
    me. On both Twitter and GitHub, I am jagthedrummer.
  • 5:56 - 5:59
    You can email me at octolabs or check out
  • 5:59 - 6:00
    my website.
  • 6:00 - 6:04
    A next bit of context is about the app.
  • 6:04 - 6:09
    CloudHDR is a HDR photo-processing automation
    web app. And
  • 6:09 - 6:11
    basically what it does is take a couple of
  • 6:11 - 6:14
    photos that look like this that aren't very
    good.
  • 6:14 - 6:17
    One's overexposed. One's a little underexposed.
    And turn it
  • 6:17 - 6:21
    into something that's a lot more attractive.
  • 6:21 - 6:26
    So, goals and requirements. It's always important
    to understand
  • 6:26 - 6:29
    what your goals and requirements are, so that
    you
  • 6:29 - 6:32
    know if you're getting there. And if your
    goals
  • 6:32 - 6:35
    and requirements are different than mine,
    you might make
  • 6:35 - 6:37
    different choices than I made.
  • 6:37 - 6:41
    So, my goal was to have small, focused apps,
  • 6:41 - 6:44
    that were easy to test, easy to run, quick
  • 6:44 - 6:48
    to deploy. I wanted single sign on and single
  • 6:48 - 6:52
    sign off across several services. I wanted
    to minimize
  • 6:52 - 6:54
    code duplication. Mainly, I did not want to
    build
  • 6:54 - 6:56
    log in screens for each one of these apps,
  • 6:56 - 6:59
    and certainly didn't want people to have to
    log
  • 6:59 - 7:01
    in more than once. And I wanted to support
  • 7:01 - 7:04
    a variety of service types. I want some apps
  • 7:04 - 7:07
    that are more or less vanilla Rails. I want
  • 7:07 - 7:10
    some that were Ember apps that were talking
    to
  • 7:10 - 7:14
    Rails JSON APIs. I've got one that's kind
    of
  • 7:14 - 7:16
    a weird hybrid between a Rails app and a
  • 7:16 - 7:18
    Ember app. And I wanted something that's gonna
    work
  • 7:18 - 7:20
    for all of these things.
  • 7:20 - 7:24
    So, OAuth2. This is kind of the meat of
  • 7:24 - 7:27
    what the talk is about. If you've ever encountered
  • 7:27 - 7:30
    login with Facebook, login with Twitter, login
    with GitHub,
  • 7:30 - 7:34
    any of these things, you've encountered OAuth2.
    If you've
  • 7:34 - 7:37
    ever implemented any of these things, then
    you're halfway
  • 7:37 - 7:40
    there to being able to do SOAuth, as I'm
  • 7:40 - 7:42
    gonna talk about it today.
  • 7:42 - 7:48
    So, a brief overview of how OAuth2 works,
    or
  • 7:48 - 7:50
    maybe a review if you've seen this before.
    This
  • 7:50 - 7:54
    is a fairly simplified version of an OAuth
    request
  • 7:54 - 7:56
    sequence.
  • 7:56 - 7:58
    So, let's say you've got a web client. It's
  • 7:58 - 8:00
    just a browser, and they're gonna try to access
  • 8:00 - 8:03
    a page. So they send a GET request to
  • 8:03 - 8:08
    some protected resource on a Rails app. That
    Rails
  • 8:08 - 8:10
    app's gonna say, oh, you're not logged in.
    I
  • 8:10 - 8:12
    don't know who you are, I can't let you
  • 8:12 - 8:14
    see this thing yet.
  • 8:14 - 8:16
    So what it does is then issue a redirect
  • 8:16 - 8:20
    to an OAuth provider and say, hey, provider,
    can
  • 8:20 - 8:23
    you please op, authenticate this guy and tell
    me
  • 8:23 - 8:25
    who he is so that I know whether I'm
  • 8:25 - 8:28
    allowed to show him this stuff or not. Browser's
  • 8:28 - 8:32
    gonna follow that redirect. Go to the provider.
    If
  • 8:32 - 8:34
    we assume that you've already logged in at
    the
  • 8:34 - 8:35
    provider, and this would be, you know, the
    case
  • 8:35 - 8:39
    if somebody's already logged into your monolith,
    and then
  • 8:39 - 8:41
    you're gonna give them a link to some supporting
  • 8:41 - 8:44
    service that they need to get to. They're
    gonna
  • 8:44 - 8:46
    already be logged into the provider, but they
    need
  • 8:46 - 8:50
    to be authenticated to the service.
  • 8:50 - 8:53
    So the provider's gonna then say, oh yeah,
    I
  • 8:53 - 8:55
    know who that guy is and I'm gonna redirect
  • 8:55 - 8:59
    back to where I came from. Browsers are gonna
  • 8:59 - 9:01
    follow the redirect again. And then when it
    gets
  • 9:01 - 9:06
    to the consumer of the OAuth services, it's
    gonna
  • 9:06 - 9:09
    then post back to the provider and say, hey,
  • 9:09 - 9:11
    somebody just showed up with a token claiming
    to
  • 9:11 - 9:14
    be a valid user. Can you tell me if
  • 9:14 - 9:17
    he is or not? The provider's gonna say, oh
  • 9:17 - 9:20
    yeah, here's a, here's a token that you can
  • 9:20 - 9:23
    use over and over to get information on behalf
  • 9:23 - 9:27
    of this user. Then the consumer is gonna say,
  • 9:27 - 9:29
    OK great. I'm gonna use that token to get
  • 9:29 - 9:33
    some more information about the user. User
    name. Email
  • 9:33 - 9:35
    address. That kind of stuff.
  • 9:35 - 9:37
    The provider's gonna respond with the JSON
    payload that
  • 9:37 - 9:41
    tells the consumer more information about
    that user, and
  • 9:41 - 9:45
    then finally the consumer is gonna redirect
    back to
  • 9:45 - 9:47
    the resource that the user was trying to get
  • 9:47 - 9:49
    to in the first place. In this case the
  • 9:49 - 9:51
    protected route.
  • 9:51 - 9:56
    Again, the browser's gonna follow the redirect,
    and finally
  • 9:56 - 9:57
    we have the page that the user wants to
  • 9:57 - 10:03
    see. Did anybody count how many request and
    response
  • 10:03 - 10:06
    pairs that was that we just looked at? Many.
  • 10:06 - 10:09
    That's a good answer.
  • 10:09 - 10:12
    That was seven. Seven. And that's in a simplified
  • 10:12 - 10:15
    version of, of this. I left out a couple
  • 10:15 - 10:17
    of redirects in there that aren't really all
    that
  • 10:17 - 10:19
    informative. So this kind of seems like a
    pain
  • 10:19 - 10:21
    in the ass to have to implement all of
  • 10:21 - 10:24
    this stuff. But luckily we have RubyGems,
    come to
  • 10:24 - 10:26
    the rescue and give us a lot of gems
  • 10:26 - 10:29
    that are gonna make this easier.
  • 10:29 - 10:34
    Namely, for the provider implementation, there's
    a gem called
  • 10:34 - 10:36
    doorkeeper. If you happened to be in the refactoring
  • 10:36 - 10:41
    workshop yesterday, that Tute ran, he is now
    the
  • 10:41 - 10:45
    new maintainer of that gem. And then for consumer
  • 10:45 - 10:49
    implementation, there's a, a gem from intridea
    called omniauth,
  • 10:49 - 10:53
    that handles most of the consumer tasks.
  • 10:53 - 10:58
    So, the plan, basically, is to stand up an
  • 10:58 - 11:01
    OAuth2 provider. This might something that
    you add to
  • 11:01 - 11:05
    your monolith. And then to create a tiny gem
  • 11:05 - 11:08
    called, in this case it's gonna be called
    so_auth.
  • 11:08 - 11:09
    When you're doing this for your company or
    for
  • 11:09 - 11:12
    your project, the name of your gem might be
  • 11:12 - 11:16
    my_comp_auth or acme_auth or whatever your,
    whatever you want
  • 11:16 - 11:17
    to call it.
  • 11:17 - 11:19
    Then we're gonna use that gem to plug it
  • 11:19 - 11:23
    into a Rails app to delegate authentication
    to the
  • 11:23 - 11:26
    provider, and then once we can do that, it's
  • 11:26 - 11:28
    real easy to stand up other services that
    are
  • 11:28 - 11:29
    doing the same thing.
  • 11:29 - 11:33
    So, real quick, a demo to give you an
  • 11:33 - 11:36
    idea of kind of what this all looks like.
  • 11:36 - 11:41
    So here, I have a, a client app, and
  • 11:41 - 11:42
    from this you can tell that I sorely need
  • 11:42 - 11:48
    to attend the design for developers talk tomorrow.
    So
  • 11:48 - 11:50
    there's links where you can go back over to
  • 11:50 - 11:53
    the provider or back to the client. And so
  • 11:53 - 11:55
    here on the client is a link to some
  • 11:55 - 11:57
    private stuff.
  • 11:57 - 12:00
    When I click on that, it is going to
  • 12:00 - 12:02
    do a couple of redirects, and then I'm going
  • 12:02 - 12:04
    to end up at the sign in page on
  • 12:04 - 12:11
    the provider. Once I sign in here, then it
  • 12:15 - 12:18
    is going to redirect me back to where I
  • 12:18 - 12:25
    originally wanted to be.
  • 12:26 - 12:33
    Well. This was working immediately before
    the talk, and
  • 12:35 - 12:39
    for some reason it's not happy now. Come on.
  • 12:39 - 12:43
    Show me the private stuff. There we go. All
  • 12:43 - 12:44
    right. So now I'm, I'm there. I can see
  • 12:44 - 12:47
    the private stuff. If I go back over to
  • 12:47 - 12:51
    the provider and I sign out and then go
  • 12:51 - 12:53
    back to the client and try to see the
  • 12:53 - 12:56
    private stuff again, it's gonna know that
    I'm signed
  • 12:56 - 12:58
    out and ask me to sign in again.
  • 12:58 - 13:02
    Why is it taking so long? There we go.
  • 13:02 - 13:04
    All right. So that's kind of what it looks
  • 13:04 - 13:08
    like in the browser. Here are a bunch of
  • 13:08 - 13:11
    links. The one on the top is just a
  • 13:11 - 13:13
    link to a page on my site that contains
  • 13:13 - 13:15
    all of these other links as well as a
  • 13:15 - 13:20
    link to these slides. There's three repos
    involved here.
  • 13:20 - 13:23
    One is for a demo provider. One is for
  • 13:23 - 13:26
    the so_auth gem itself, and the other is for
  • 13:26 - 13:29
    the so_auth client demo.
  • 13:29 - 13:31
    And the so_auth gem itself kind of connects
    the
  • 13:31 - 13:35
    provider and the consumer. And then there
    are a
  • 13:35 - 13:37
    couple of demo links there for where these
    are
  • 13:37 - 13:42
    hosted on Heroku where you can see it live.
  • 13:42 - 13:46
    So, just doing this all as a provider and
  • 13:46 - 13:48
    then all as a consumer makes it a little
  • 13:48 - 13:51
    hard to follow the code and to understand
    what
  • 13:51 - 13:55
    code is responsible for what parts of that
    request
  • 13:55 - 13:57
    sequence that we looked at. So instead of
    trying
  • 13:57 - 13:59
    to do it all in two big chunks, we're
  • 13:59 - 14:01
    just kind of going to step through the sequence
  • 14:01 - 14:04
    that we looked at and implement or integrate
    the
  • 14:04 - 14:06
    parts that we need as we need them.
  • 14:06 - 14:08
    So that means that the first thing we look
  • 14:08 - 14:11
    at is the first request cycle, which is the
  • 14:11 - 14:14
    client tries to get some protected stuff and
    then
  • 14:14 - 14:16
    needs to be redirected to the provider. So
    that
  • 14:16 - 14:19
    means we're gonna start with the consumer
    implementation. And
  • 14:19 - 14:23
    this is part one of the consumer implementation.
  • 14:23 - 14:25
    And so this is all about entering the OAuth
  • 14:25 - 14:29
    dance. And that's what that huge redirect
    sequence that
  • 14:29 - 14:34
    we saw earlier is sometimes referred to. So,
    like
  • 14:34 - 14:35
    I mentioned, we want to do this in a
  • 14:35 - 14:39
    standalone gem that can be incorporated into
    other projects
  • 14:39 - 14:41
    so that we don't have to reimplement this
    for
  • 14:41 - 14:44
    every new single that we want to stand up.
  • 14:44 - 14:44
    So to do that we just start a new
  • 14:44 - 14:49
    Rails plugin. I did a full, because I don't
  • 14:49 - 14:51
    want to have to mess with mounting routes
    in
  • 14:51 - 14:53
    each of my new services. I just want to
  • 14:53 - 14:55
    be able to assume that routes are gonna be
  • 14:55 - 14:58
    available and are gonna always do what I expect
  • 14:58 - 15:00
    them to do in each one of these services.
  • 15:00 - 15:04
    If you're building something for, you know,
    other developers
  • 15:04 - 15:07
    to consume that are outside of your organization,
    that
  • 15:07 - 15:09
    might not be the right choice. And then I'm
  • 15:09 - 15:11
    telling it where to put the, the dummy app
  • 15:11 - 15:16
    that we can use to test this thing.
  • 15:16 - 15:17
    So the first bit is update the gem spec
  • 15:17 - 15:20
    for this auth, or this gem to tell it
  • 15:20 - 15:25
    that we need onmiauth and the omniauth-oath2
    gem to
  • 15:25 - 15:28
    handle some of this stuff for us.
  • 15:28 - 15:30
    So then we need to create a strategy for
  • 15:30 - 15:33
    OmniAuth. And that strategy is basically a
    little bit
  • 15:33 - 15:37
    of code that you write that tells OmniAuth
    where
  • 15:37 - 15:40
    to find your provider and how to deal with
  • 15:40 - 15:43
    information that it gets back from it. And
    so
  • 15:43 - 15:44
    for the first part, all we have to do
  • 15:44 - 15:47
    is tell it where the provider lives and how
  • 15:47 - 15:49
    to find it. So we give it the, the
  • 15:49 - 15:52
    name of the strategy that we want to use,
  • 15:52 - 15:54
    and then set some client options. Just tell
    it
  • 15:54 - 15:57
    the site, which is the url of the provider
  • 15:57 - 16:00
    itself. And then two end points. Where do
    I
  • 16:00 - 16:02
    go to authorize a user and where do I
  • 16:02 - 16:07
    go to find a token about the user?
  • 16:07 - 16:08
    And then you have to register that strategy
    with
  • 16:08 - 16:13
    OmniAuth, and this is done in a initializer.
    You
  • 16:13 - 16:15
    really probably want to build a generator
    for this
  • 16:15 - 16:17
    into your gem so that any new project that
  • 16:17 - 16:21
    you stand up, you can just create this initializer
  • 16:21 - 16:23
    and that's it.
  • 16:23 - 16:26
    You're just basically telling OmniAuth, use
    a provider called
  • 16:26 - 16:29
    :so, and here's an APP_ID and an APP_SECRET
    that's
  • 16:29 - 16:35
    used to authenticate that application to the
    provider. Then
  • 16:35 - 16:37
    we're gonna create a little content in the
    dummy
  • 16:37 - 16:41
    application that comes with this gem. Rails
    g controller
  • 16:41 - 16:44
    stuff. Give it a private and a public method.
  • 16:44 - 16:46
    The public stuff anybody can see. The private
    stuff
  • 16:46 - 16:49
    is what we want to have protected.
  • 16:49 - 16:51
    And so to protect it, we're gonna wanna do
  • 16:51 - 16:53
    something like this. We're gonna wanna be
    able to
  • 16:53 - 16:59
    say, before_filter, login is required for
    the private action.
  • 16:59 - 17:01
    We don't actually have that before_filter
    yet, so we're
  • 17:01 - 17:03
    gonna have to build it. But we don't want
  • 17:03 - 17:05
    to build it directly into the dummy app. We
  • 17:05 - 17:08
    want to build it in the, in the gem,
  • 17:08 - 17:09
    so we need to tell the dummy app that
  • 17:09 - 17:13
    its ApplicationController should inherit from
    the ApplicationController that comes
  • 17:13 - 17:16
    with our gem.
  • 17:16 - 17:20
    And so then, in the gem, we can define
  • 17:20 - 17:23
    the login_required method, and at this point,
    we just
  • 17:23 - 17:25
    know, nobody's authorized. So we can just
    call our
  • 17:25 - 17:29
    not_authorized method, which is gonna redirect
    to auth slash
  • 17:29 - 17:33
    so. And that is the, the interface to tell
  • 17:33 - 17:37
    OmniAuth that you want to start the OAuth
    dance
  • 17:37 - 17:41
    with the so provider.
  • 17:41 - 17:47
    So, once that's done, OmniAuth is gonna handle
    doing
  • 17:47 - 17:50
    the redirect to where the provider lives based
    on
  • 17:50 - 17:53
    what we told it in the strategy of, of
  • 17:53 - 17:56
    where that, where that provider lives and
    what end
  • 17:56 - 18:01
    points it should hit. And so then the provider
  • 18:01 - 18:04
    is gonna, at that point, need to redirect
    back
  • 18:04 - 18:06
    to the consumer.
  • 18:06 - 18:08
    So that means we need to step on to
  • 18:08 - 18:12
    the provider implementation. The doorkeeper
    gem, for this, is
  • 18:12 - 18:16
    really easy to use. You pretty much are just
  • 18:16 - 18:18
    gonna follow the directions that are in the
    README,
  • 18:18 - 18:21
    which is add the gem to your gemfile, do
  • 18:21 - 18:26
    bundle install, generate the doorkeeper install,
    which is an
  • 18:26 - 18:30
    initializer for it. Generate a migration so
    that mi,
  • 18:30 - 18:32
    that doorkeeper has some tables to keep all
    of
  • 18:32 - 18:35
    the, the bookkeeping that it needs to do.
    And
  • 18:35 - 18:37
    then you're gonna migrate your database.
  • 18:37 - 18:40
    So, the doorkeeper configuration, there are
    really just two
  • 18:40 - 18:43
    parts of it that are really interesting for
    this
  • 18:43 - 18:45
    talk that we need to look at. One is
  • 18:45 - 18:50
    a block in the configuration called resource_owner_authenticator,
    and the,
  • 18:50 - 18:54
    the job of this block is to either return
  • 18:54 - 18:58
    a currently logged in user or to redirect
    to
  • 18:58 - 19:01
    the place where the, the user can log in.
  • 19:01 - 19:04
    And, for this block, I'm assuming that we're
    gonna
  • 19:04 - 19:08
    be adding this to a devise application, and
    devise
  • 19:08 - 19:11
    is built on top of warden, so in this
  • 19:11 - 19:15
    case, we just want to tell warden to authenticate
  • 19:15 - 19:18
    with the user scope. That scope might be different
  • 19:18 - 19:20
    if your user's not called user. You know,
    if
  • 19:20 - 19:27
    it's admin_user or person or whatever it might
    be.
  • 19:31 - 19:33
    So the next bit is that doorkeeper comes with
  • 19:33 - 19:36
    an application authorization screen. This
    is basically the same
  • 19:36 - 19:39
    thing that you see any time you do sign
  • 19:39 - 19:42
    in with GitHub for the first time on a
  • 19:42 - 19:45
    new application, and you see the, the screen
    that
  • 19:45 - 19:48
    says, hey, application X would like to have
    access
  • 19:48 - 19:50
    to your profile, or would like to be able
  • 19:50 - 19:53
    to Tweet for you or any of those things
  • 19:53 - 19:59
    that applications can ask for when you're
    delegating authentication.
  • 19:59 - 20:01
    And for the purposes of doing a completely
    internal
  • 20:01 - 20:05
    provider that's only gonna ever have other
    internal services,
  • 20:05 - 20:07
    you really don't need that screen. And it's
    gonna
  • 20:07 - 20:09
    be confusing to your users. They're, they're
    not gonna
  • 20:09 - 20:11
    understand, and it doesn't really need to
    be there.
  • 20:11 - 20:14
    So we can just get rid of that entirely.
  • 20:14 - 20:16
    And the way you do that with doorkeeper is
  • 20:16 - 20:21
    to implement the skip_authorization block,
    and just return true.
  • 20:21 - 20:23
    Just tell it, always skip authorization because
    we don't
  • 20:23 - 20:26
    need it. If you're doing something that's
    gonna be
  • 20:26 - 20:29
    more public, you're trying to be the next
    Facebook,
  • 20:29 - 20:31
    you probably don't want to skip the authorization
    screen,
  • 20:31 - 20:33
    and instead are gonna need it. But maybe you
  • 20:33 - 20:36
    want to skip it for admin users or something
  • 20:36 - 20:38
    like that. This is just a block of code
  • 20:38 - 20:41
    that runs within the context of your app.
  • 20:41 - 20:43
    So you can do anything that you want there.
  • 20:43 - 20:46
    Implement any kind of logic to ultimately
    return true
  • 20:46 - 20:50
    or false on, should we skip authorization
    or not?
  • 20:50 - 20:53
    So, all right. Hurray gems. They're getting
    us most
  • 20:53 - 20:54
    of the way there already.
  • 20:54 - 20:59
    The, the request for the authorization end
    points can
  • 20:59 - 21:02
    happen, and doorkeeper is gonna handle doing
    the redirect
  • 21:02 - 21:05
    back to the consumer app, once it knows that
  • 21:05 - 21:09
    we have a valid user.
  • 21:09 - 21:11
    Browser's love redirects, so it's gonna follow
    it. Gonna
  • 21:11 - 21:15
    go back to the consumer. And then OmniAuth
    is
  • 21:15 - 21:19
    gonna handle for us, POSTing to the provider
    to
  • 21:19 - 21:23
    try to get a token. Doorkeeper's gonna handle
    returning
  • 21:23 - 21:28
    that token to the consumer. Again, OmniAuth
    and so_auth
  • 21:28 - 21:31
    combined, based on the strategy that we saw
    earlier,
  • 21:31 - 21:34
    is gonna send a GET request to get some
  • 21:34 - 21:38
    more information about the user. And then,
    at this
  • 21:38 - 21:40
    point, we need to return some information.
  • 21:40 - 21:43
    But doorkeeper can't do that for you automatically,
    because
  • 21:43 - 21:47
    this is very application dependent. Doorkeeper
    doesn't know what
  • 21:47 - 21:50
    your user model is. It doesn't know which
    attributes
  • 21:50 - 21:53
    from your user model you might want to send.
  • 21:53 - 21:56
    So you have to implement this yourself.
  • 21:56 - 21:58
    But it's real easy to do. You're just gonna
  • 21:58 - 22:01
    add a route for oauth slash me, and tell
  • 22:01 - 22:05
    it which controller to use. And then in that
  • 22:05 - 22:08
    controller action, it can be just as simple
    as
  • 22:08 - 22:14
    respond_with the current_resource_owner. And
    the current, so, I should
  • 22:14 - 22:15
    back a little bit.
  • 22:15 - 22:18
    So that first line there, doorkeeper_for :all,
    says that
  • 22:18 - 22:21
    anything within this controller needs to be
    protected by
  • 22:21 - 22:24
    doorkeeper, and we should only allow access
    to the
  • 22:24 - 22:27
    actions if that request comes in with a valid
  • 22:27 - 22:31
    token that belongs to a user.
  • 22:31 - 22:32
    And so what that does is actually set up
  • 22:32 - 22:38
    the doorkeeper_token variable for us that
    we can use.
  • 22:38 - 22:40
    So then the current_resource_owner, we just
    do user dot
  • 22:40 - 22:46
    find. Whichever owner, whichever user is the
    owner of
  • 22:46 - 22:49
    the token that was passed in for this request.
  • 22:49 - 22:51
    And so then in the me action, we just
  • 22:51 - 22:54
    respond with that user. Let Rails do its thing
  • 22:54 - 22:58
    for serializing the user to JSON and putting
    it
  • 22:58 - 23:01
    back out over the wire.
  • 23:01 - 23:03
    So we're getting really close. So at this
    point
  • 23:03 - 23:06
    we've some JSON payload coming back. From
    the provider
  • 23:06 - 23:12
    to the consumer. But then what happens? At
    the
  • 23:12 - 23:15
    consumer, something needs to happen to stand
    up a
  • 23:15 - 23:19
    session and let it know that we do have
  • 23:19 - 23:21
    a, a valid user logged in at this point.
  • 23:21 - 23:25
    And, again, this is pretty application specific.
    And so
  • 23:25 - 23:27
    you need to implement this yourself. OmniAuth
    can't do
  • 23:27 - 23:29
    it for you.
  • 23:29 - 23:30
    But the good thing is is we can just
  • 23:30 - 23:32
    do this once in the so_auth gem, and then
  • 23:32 - 23:35
    reuse that gem in all of our services. Not
  • 23:35 - 23:37
    have to reimplement it every time.
  • 23:37 - 23:40
    So, that means we're back to the consumer
    implementation.
  • 23:40 - 23:44
    Part two.
  • 23:44 - 23:46
    And this is all about standing up a session
  • 23:46 - 23:52
    and exiting the OAuth dance. So back to the
  • 23:52 - 23:54
    strategy that we looked at earlier, we need
    a
  • 23:54 - 23:57
    method that allows us to get just raw user
  • 23:57 - 24:01
    information from the provider. To do this,
    we just
  • 24:01 - 24:05
    use the access_token that is provided for
    us by
  • 24:05 - 24:09
    OmniAuth, and tell it to get oauth slash me.
  • 24:09 - 24:12
    OmniAuth is gonna handle plugin on the access_token
    so
  • 24:12 - 24:17
    that doorkeeper knows how to identify the
    user. And
  • 24:17 - 24:20
    then we tell it how to find the uid
  • 24:20 - 24:25
    for that user. Where in the JSON to find
  • 24:25 - 24:28
    a, a user id.
  • 24:28 - 24:31
    And then OmniAuth also wants two extra hashes
    of
  • 24:31 - 24:34
    information to be available. One's called
    info, and in
  • 24:34 - 24:37
    that, I just pull out all the bits from
  • 24:37 - 24:40
    that JSON payload that we saw earlier. All
    the
  • 24:40 - 24:43
    bits that I care about that are gonna be
  • 24:43 - 24:46
    used in the service. And then for extra, I
  • 24:46 - 24:51
    just plugin the whole payload from the provider
    itself,
  • 24:51 - 24:53
    so that if the service needs to do something
  • 24:53 - 24:57
    extra with it, that information will be available
    and
  • 24:57 - 25:00
    be handled in the service.
  • 25:00 - 25:07
    So, to, to do the final bit of. What
  • 25:07 - 25:11
    did I do? There we go. The final bit
  • 25:11 - 25:15
    is to implement two callbacks that OmniAuth
    uses to
  • 25:15 - 25:20
    exit the OAuth dance. If OmniAuth detects
    that it
  • 25:20 - 25:24
    got valid user information back, it's gonna
    redirect to
  • 25:24 - 25:28
    auth slash so slash callback. And again if
    your,
  • 25:28 - 25:30
    if your strategy is called something different,
    that's gonna
  • 25:30 - 25:32
    be a different route.
  • 25:32 - 25:35
    And if it can't identify that a user did
  • 25:35 - 25:38
    come back, it's gonna redirect to auth/failure.
    So we
  • 25:38 - 25:41
    just set up those two routes, tell it what
  • 25:41 - 25:44
    controller to go to.
  • 25:44 - 25:47
    In the create method, we're gonna grab ahold
    of
  • 25:47 - 25:52
    some environment variables that OmniAuth sets
    up for us,
  • 25:52 - 25:55
    and then from there we're gonna find_or_create
    a user
  • 25:55 - 25:59
    based on the id that OmniAuth pulled out of
  • 25:59 - 26:02
    that JSON payload, which is knows how to do
  • 26:02 - 26:06
    because of the strategy that we set up and
  • 26:06 - 26:09
    registered with OmniAuth.
  • 26:09 - 26:12
    At that point, we're also gonna update their
    email
  • 26:12 - 26:16
    address, bio. Any other information that's
    delivered in that
  • 26:16 - 26:19
    payload, because any time that that information
    changes at
  • 26:19 - 26:22
    the provider, we want it to filter down to
  • 26:22 - 26:26
    the consumer on each new authorization, so
    that we
  • 26:26 - 26:30
    don't get stale user info there.
  • 26:30 - 26:31
    So then we're just gonna stick the user session,
  • 26:31 - 26:34
    or the user id into the session, so that
  • 26:34 - 26:36
    we know we have a valid user. And then
  • 26:36 - 26:41
    redirect to OmniAuth dot origin, which is
    an environment
  • 26:41 - 26:44
    variable, again, that OmniAuth sets up in
    order to
  • 26:44 - 26:47
    handle getting back to where the user was
    trying
  • 26:47 - 26:51
    to go in the first place.
  • 26:51 - 26:56
    The failure route, what I've implemented is
    very rough.
  • 26:56 - 26:59
    Basically just pull the notice out of the,
    the
  • 26:59 - 27:02
    request that OmniAuth sets up, and then redirect
    back
  • 27:02 - 27:07
    to the root path. This isn't very elegant.
    You
  • 27:07 - 27:10
    know, you could definitely do better failure
    handling than
  • 27:10 - 27:12
    this. But this will get the job done and
  • 27:12 - 27:17
    communicate what's happening.
  • 27:17 - 27:20
    So then, back at the, the application controller
    in
  • 27:20 - 27:21
    our so_auth gem, we can add a few more
  • 27:21 - 27:24
    convenience methods that make it easy to deal
    with
  • 27:24 - 27:28
    users that are coming from the provider. So
    first
  • 27:28 - 27:31
    we just add a current_user method that will
    look
  • 27:31 - 27:34
    for a user id in the session, and if
  • 27:34 - 27:36
    it finds it, it's gonna look up that user
  • 27:36 - 27:38
    and return it.
  • 27:38 - 27:42
    A signed_in? convenience method that just
    checks to see,
  • 27:42 - 27:44
    hey, do we have a, a current_user? And then
  • 27:44 - 27:47
    we're gonna register both of those as helpers.
    I
  • 27:47 - 27:50
    did this, you know, I chose these names and
  • 27:50 - 27:54
    methods because it's very similar to devise,
    and so
  • 27:54 - 27:57
    I'm using devise on my provider. I'm using
    something
  • 27:57 - 28:00
    that acts like devise from a API stand point
  • 28:00 - 28:03
    in my consumers. So that means I have a
  • 28:03 - 28:07
    consistent way of dealing with users from
    provider and
  • 28:07 - 28:09
    consumer.
  • 28:09 - 28:12
    And so then we can modify the login required
  • 28:12 - 28:16
    method, just barely. So now we just say, tell
  • 28:16 - 28:18
    them that they're not authorized unless we
    have a
  • 28:18 - 28:19
    current_user.
  • 28:19 - 28:25
    So, hooray. Now we are getting out of the,
  • 28:25 - 28:28
    the OAuth dance and are gonna be redirected
    back
  • 28:28 - 28:31
    to the protected method. The user is gonna
    see
  • 28:31 - 28:35
    what they wanted to see in the first place.
  • 28:35 - 28:38
    So there's one last bit that isn't handled
    automatically
  • 28:38 - 28:42
    by doorkeeper and OmniAuth, and that's single
    sign off.
  • 28:42 - 28:44
    And you know, this is important. It's something
    that
  • 28:44 - 28:46
    you definitely want to do, because you don't
    want
  • 28:46 - 28:50
    somebody to log off of your consumer app and,
  • 28:50 - 28:52
    unbeknownst to them, still be logged in to
    the
  • 28:52 - 28:55
    provider. Or vise versa. You don't want them
    to
  • 28:55 - 28:57
    log out of the provider but still have a
  • 28:57 - 29:01
    user session going on the consumer.
  • 29:01 - 29:03
    It's unexpected behavior and, depending on
    the nature of
  • 29:03 - 29:05
    your app, could be, you know, a serious security
  • 29:05 - 29:10
    leak. So on the provider, this is really pretty
  • 29:10 - 29:16
    easy. The, the strategy is to set a cookie
  • 29:16 - 29:18
    on the user's machine any time that they log
  • 29:18 - 29:23
    in that contains their user id. And this isn't
  • 29:23 - 29:25
    ever used for looking up the user. This is
  • 29:25 - 29:30
    only used to verify that the consumer and
    the
  • 29:30 - 29:34
    producer, or, and the consumer and the provider
    agree
  • 29:34 - 29:38
    on who is the currently signed in user.
  • 29:38 - 29:41
    And then we want to have a remove_user_cookie
    method
  • 29:41 - 29:42
    that's gonna be fired any time that the user
  • 29:42 - 29:46
    logs out. And that's gonna handle removing
    that cookie
  • 29:46 - 29:51
    so that the, the consumer can see that, oh.
  • 29:51 - 29:55
    Somebody's logged out.
  • 29:55 - 29:57
    So then to hook this up with devise is
  • 29:57 - 30:00
    pretty easy. In your devise_for :users block
    in the
  • 30:00 - 30:01
    routes, you're gonna tell it that you want
    to
  • 30:01 - 30:06
    use a custom SessionsController, and then
    you're gonna implement
  • 30:06 - 30:12
    a new controller that inherits from Devise::SessionsController.
    And then,
  • 30:12 - 30:15
    in our create and destroy methods, for both,
    we're
  • 30:15 - 30:17
    just gonna call super and pass it a block.
  • 30:17 - 30:20
    And, so devise makes it really easy to stick
  • 30:20 - 30:23
    in little bits of extra functionality around
    what it
  • 30:23 - 30:25
    provides for you. And in the case of the
  • 30:25 - 30:29
    create method, what we're basically saying
    is, all right,
  • 30:29 - 30:32
    devise, do your thing. Create the user session.
    Get
  • 30:32 - 30:35
    them logged in. And after you do that, but
  • 30:35 - 30:39
    before you redirect, let me do a little thing.
  • 30:39 - 30:42
    And so after we have the session stood up
  • 30:42 - 30:44
    but before we redirect back to the consumer,
    we
  • 30:44 - 30:46
    want to set that user cookie.
  • 30:46 - 30:49
    Same kind of thing with destroy. After devise
    has
  • 30:49 - 30:52
    destroyed the user session on the provider,
    but before
  • 30:52 - 30:55
    they'd, before it redirects back to wherever
    it's gonna
  • 30:55 - 30:57
    try to redirect, we want to remove the user
  • 30:57 - 31:00
    cookie.
  • 31:00 - 31:02
    So then the next bit is the single sign
  • 31:02 - 31:05
    off for the consumer side. And so there's
    a
  • 31:05 - 31:10
    before_filter called :check_cookie. And this
    says, reset the session
  • 31:10 - 31:13
    unless we have a valid cookie. And to determine
  • 31:13 - 31:15
    if we have a valid cookie, there's three things
  • 31:15 - 31:16
    we need to check.
  • 31:16 - 31:19
    One. Do we have a cookie called so_auth that
  • 31:19 - 31:21
    has a value in it? Do we have a
  • 31:21 - 31:24
    user id in our session? And do those two
  • 31:24 - 31:27
    things match? And if all three of those things
  • 31:27 - 31:30
    are true, then we have a valid user and
  • 31:30 - 31:32
    the cookie is still valid. If any of those
  • 31:32 - 31:35
    three things are not true, it's not a valid
  • 31:35 - 31:38
    cookie and we should reset the session because
    either
  • 31:38 - 31:41
    somebody's logged out, or they logged out
    at the
  • 31:41 - 31:43
    provider and then logged back in as a different
  • 31:43 - 31:45
    user.
  • 31:45 - 31:47
    So this helps you not have that kind of
  • 31:47 - 31:49
    leakage.
  • 31:49 - 31:51
    And then the last bit on the consumer side
  • 31:51 - 31:53
    is you want to implement a log out route,
  • 31:53 - 31:57
    so that if somebody logs out on your consumer,
  • 31:57 - 32:00
    you can handle getting them logged out on
    the
  • 32:00 - 32:03
    provider as well. And that just looks like
    this.
  • 32:03 - 32:05
    When somebody tries to log out, we reset the
  • 32:05 - 32:10
    session and then redirect to the provider
    sign out
  • 32:10 - 32:14
    route. That's gonna trigger the provider to
    remove that
  • 32:14 - 32:16
    cookie so that next time they're back at the
  • 32:16 - 32:20
    consumer. The consumer knows, hey, we no longer
    have
  • 32:20 - 32:21
    a valid session.
  • 32:21 - 32:23
    And this is important because if you've got
    multiple
  • 32:23 - 32:26
    consumers and somebody logs out of one consumer,
    you
  • 32:26 - 32:28
    want them to be logged out of all of
  • 32:28 - 32:30
    those other consumers simultaneously.
  • 32:30 - 32:33
    So, now that we've got a little gem to
  • 32:33 - 32:36
    use this, we can use it in new Rails
  • 32:36 - 32:39
    apps to stand up new services. So then we're
  • 32:39 - 32:41
    just gonna add it to the, oh. So we're
  • 32:41 - 32:45
    gonna create a new Rails app called so_auth
    consumer.
  • 32:45 - 32:47
    And then in the gem file we're gonna point
  • 32:47 - 32:51
    to this new so_auth gem that we've just made.
  • 32:51 - 32:53
    For development, you can just point at the
    path
  • 32:53 - 32:57
    that's on your file system. For development
    I would
  • 32:57 - 33:01
    recommend using the vendorize gem, and this
    just basically
  • 33:01 - 33:03
    gives you a rake task that you can tell
  • 33:03 - 33:06
    it, hey, here's an address of a git repo
  • 33:06 - 33:09
    that I want to have vendored. That's gonna
    be,
  • 33:09 - 33:12
    it's just gonna pull the, the git repo, put
  • 33:12 - 33:14
    it in your vendor path, and then any time
  • 33:14 - 33:16
    that you need to update that gem, you run
  • 33:16 - 33:19
    exactly the same rake command again and you're
    gonna
  • 33:19 - 33:26
    have the newest version of your custom auth
    gem.
  • 33:26 - 33:29
    Then you're gonna generate the, the initializer.
    This is
  • 33:29 - 33:31
    just exactly the same initializer that we
    looked at
  • 33:31 - 33:34
    earlier, but you've got it in a nice generator
  • 33:34 - 33:38
    so that you don't actually have to write it.
  • 33:38 - 33:41
    So then the next bit is on the so_auth
  • 33:41 - 33:44
    provider, you need to go to the oath slash
  • 33:44 - 33:48
    applications route, which is provided by doorkeeper,
    and register
  • 33:48 - 33:52
    a new consumer application. You're gonna give
    it a
  • 33:52 - 33:55
    name and tell it what the redirect url that,
  • 33:55 - 33:59
    that that consumer expects to use. And that's,
    that's
  • 33:59 - 34:02
    done so that other applications can't try
    to spoof
  • 34:02 - 34:06
    that consumer and get your user information
    when they're
  • 34:06 - 34:10
    not really allowed to do that.
  • 34:10 - 34:13
    So once you create your new consumer in the
  • 34:13 - 34:19
    OAuth applications area, you're gonna take
    down the application,
  • 34:19 - 34:23
    application id and secret that doorkeeper
    provides, and then
  • 34:23 - 34:25
    set that in some environment variables. If
    you're using
  • 34:25 - 34:28
    something like foreman or deploying to Heroku.
    You know,
  • 34:28 - 34:30
    in Heroku you would set this up in environment
  • 34:30 - 34:33
    variables. Locally, for foreman, you'd put
    it in a
  • 34:33 - 34:36
    dot env file, so that it's there and available
  • 34:36 - 34:40
    to your application whenever it fires up.
  • 34:40 - 34:43
    And then, in your consumer app, you're gonna
    create
  • 34:43 - 34:47
    a user model and then protect some content.
    You're
  • 34:47 - 34:50
    gonna use that before_filter :login required
    that we created
  • 34:50 - 34:51
    in the gem.
  • 34:51 - 34:55
    So all right. Implementation is over and everything
    is
  • 34:55 - 35:01
    awesome. Yeah, I know. Bad. It's bad.
  • 35:01 - 35:07
    All right. So, to wrap up. Basically gonna
    stand
  • 35:07 - 35:09
    up a provider, build a convenience gem that
    allows
  • 35:09 - 35:12
    you to stand up more services, and then you
  • 35:12 - 35:15
    just start standing up services. And the one
    thing
  • 35:15 - 35:17
    that I'll hope you'll take away from this
    is
  • 35:17 - 35:20
    that, you can start exactly where you are
    today,
  • 35:20 - 35:23
    even if it looks like that. You can plug
  • 35:23 - 35:26
    on provider functionality on top of your monolith
    and
  • 35:26 - 35:29
    then you can start adding services to start
    inching
  • 35:29 - 35:31
    your way towards SOA.
  • 35:31 - 35:33
    And that's it. Thanks for watching.
Title:
RailsConf 2014 - Service Oriented Authenication by Jeremy Green
Description:

more » « less
Duration:
36:00

English subtitles

Revisions