@justin richer introduction to oauth 2 · the oauth 2.0 authorization framework enables a...

116
@justin _ _ richer https://bspk.io/ Introduction to OAuth 2.0 Justin Richer Bespoke Engineering 1

Upload: others

Post on 25-May-2020

18 views

Category:

Documents


0 download

TRANSCRIPT

@justin _ _ richer

https://bspk.io/

IntroductiontoOAuth2.0

JustinRicherBespokeEngineering

1

@justin _ _ richer

https://bspk.io/

COURSEVERSION1.6Feedbackiswelcome!

2

@justin _ _ richer

https://bspk.io/

Trythehomeedition•  OAuth2InAction•  Codeisopensource•  PublishedMarch2017

3

@justin _ _ richer

https://bspk.io/

WHATISOAUTH2.0?

4

@justin _ _ richer

https://bspk.io/

Fromthespec(RFC6749)The OAuth 2.0 authorization framework enables a third-party application to obtain limited access to an HTTP service, either on behalf of a resource owner by orchestrating an approval interaction between the resource owner and the HTTP service, or by allowing the third-party application to obtain access on its own behalf.

5

@justin _ _ richer

https://bspk.io/

ThegoodbitsThe OAuth 2.0 authorization framework enables a third-party application to obtain limited access to an HTTP service, either on behalf of a resource owner by orchestrating an approval interaction between the resource owner and the HTTP service, or by allowing the third-party application to obtain access on its own behalf.

6

@justin _ _ richer

https://bspk.io/

InotherwordsOAuth 2.0 is a delegation protocol that lets people allow applications to access things on their behalf.

7

@justin _ _ richer

https://bspk.io/

Whoisinvolved?

Resource Owner Authorization

Server

ProtectedResource

Client

8

@justin _ _ richer

https://bspk.io/

Theresourceowner•  HasaccesstosomeresourceorAPI•  CandelegateaccesstothatresourceorAPI•  Usuallyhasaccesstoawebbrowser•  Usuallyisaperson

9

@justin _ _ richer

https://bspk.io/

Theprotectedresource•  Webservice(API)withsecuritycontrols•  Protectsthingsfortheresourceowner•  Sharesthingsontheresourceowner’srequest

10

@justin _ _ richer

https://bspk.io/

Theclientapplication•  Wantstoaccesstheprotectedresource•  Doesthingsontheresourceowner’sbehalf•  Couldbeawebserver– Butit’sstilla“client”inOAuthparlance– CouldalsobeanativeapporJSapp

11

@justin _ _ richer

https://bspk.io/

Whatarewetryingtosolve?

Resource Owner

The Goal:

Give the client access to the protected

resource on behalf of the resource owner.

ProtectedResource

Client

12

@justin _ _ richer

https://bspk.io/

THISISN’TANEWPROBLEMPeoplehavebeensolvingthisforalongtime

13

@justin _ _ richer

https://bspk.io/

Stealthekeys

Resource Owner

Copy the resource owner’s credentials

and replay them to the protected resource.

ProtectedResource

Client

14

@justin _ _ richer

https://bspk.io/

Askforthekeys

Resource Owner

ProtectedResource

Client

?

Ask for the resource owner’s credentials

and replay them to the protected resource.

15

@justin _ _ richer

https://bspk.io/

Useauniversalkey

Resource Owner

A universal key that’s good for opening the door no matter who locked it.

ProtectedResource

Client

16

@justin _ _ richer

https://bspk.io/

Service-specificcredentials

Resource Owner

A special password (or token) that can be used to access just this

protected resource.

ProtectedResource

Client

17

@justin _ _ richer

https://bspk.io/

WE’REGETTINGCLOSER…

18

@justin _ _ richer

https://bspk.io/

IntroducingtheAuthorizationServer(AS)

Resource Owner Authorization

Server

ProtectedResource

Client

The Authorization Server gives us a

mechanism to bridge the gap between the client and the protected resource

19

@justin _ _ richer

https://bspk.io/

TheAuthorizationServer•  Generatestokensfortheclient•  Authenticatesresourceowners(users)•  Authenticatesclients•  Managesauthorizations

20

@justin _ _ richer

https://bspk.io/

OAuthTokens•  Representgranteddelegatedauthorities–  Fromtheresourceownertotheclientfortheprotectedresource

•  Issuedbyauthorizationserver•  Usedbyclient–  Formatisopaquetoclients

•  Consumedbyprotectedresource

21

@justin _ _ richer

https://bspk.io/

ExampleOAuthTokens•  92d42038006dba95d0c501951ac5b5eb•  2df029c6-b38d-4083-b8d9-db67c774d13f•  eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

•  waterbuffalo-elephant-helicopter-argument

22

@justin _ _ richer

https://bspk.io/

You’veusedOAuth

23

@justin _ _ richer

https://bspk.io/

ABRIEFHISTORYOFOAUTH2.0

24

@justin _ _ richer

https://bspk.io/

Circa2006•  HTTPpasswordauthenticationcommonforAPIaccess– “Givemeyourpassword”

•  Internetcompanieshaveproprietarysolutionsfordelegatedaccess– BBAuth,AuthSub,afewothers

25

@justin _ _ richer

https://bspk.io/

Theproblem•  TwosmallersiteswanttoconnecttheirAPIsfortheirusers

•  BothuseOpenIDforlogin– Nousername/passwordtopass!

•  Neitherwantstouseaproprietarysolution

26

@justin _ _ richer

https://bspk.io/

Anewstandardisborn•  OAuth1.0ispublishedindependently– Noformalstandardsbody,peoplejustuseit

•  Asessionfixationattackisfoundandfixed– NewversioniscalledOAuth1.0a

•  ThiscommunitydocumentisstandardizedasRFC5849intheIETF

27

@justin _ _ richer

https://bspk.io/

Peoplestartusingit•  OAuth1.0asolvesmajorpainpointsformanypeopleinastandardandunderstandableway

•  Google,Yahoo,andothersreplacetheirsolutionswiththenewstandard

28

@justin _ _ richer

https://bspk.io/

Peoplestartabusingit•  PeoplealsodecidetostartusingOAuthforoff-labelusecases– Nativeapplications– Nouserintheloop– Distributedauthorizationsystems

29

@justin _ _ richer

https://bspk.io/

Version2.0:Theframework•  Modularizedconcepts•  Separatedpreviouslyconflatedcomponents•  Addedexplicitextensibilitypoints•  Removedpainpointsofimplementers•  StandardizedinRFC6749andRFC6750

30

@justin _ _ richer

https://bspk.io/

Whatdoesthismean?•  Insteadofasingleprotocol,OAuth2.0definescommonconceptsandcomponentsanddifferentwaystomixthemtogether

•  It’snotasinglestandard,it’sasetofstandardsfordifferentusecases

31

@justin _ _ richer

https://bspk.io/

WHATOAUTHISN’T

32

@justin _ _ richer

https://bspk.io/

NotdefinedoutsideofHTTP•  CoreprotocoldefinedonlyforHTTP•  ReliesonTLSforsecuringmessages•  ThereareeffortstouseOAuthovernon-HTTPprotocols– GSSAPI– CoAP

33

@justin _ _ richer

https://bspk.io/

Notanauthenticationprotocol•  Reliesonauthenticationinseveralplaces– Clientauthenticationtotokenendpoint– Resourceownerauthenticationatauthorizationendpoint

•  Doesn’tcommunicateanythingabouttheuser•  However:authenticationprotocolscanbebuiltusingOAuth(OpenIDConnect)

34

@justin _ _ richer

https://bspk.io/

Nouser-to-userdelegation•  Allowsausertodelegatetoapieceofsoftwarebutnottoanotheruser

•  However,multi-partydelegationcanbebuiltusingOAuthasacorecomponent(UMA)

35

@justin _ _ richer

https://bspk.io/

Noauthorizationprocessing•  Tokenscanrepresentscopesandotherauthorizationinformation

•  Processingofthisinformationisuptotheresourceserver

•  However,severalmethods(UMA,JWT,introspection)tocommunicatethisinformation

36

@justin _ _ richer

https://bspk.io/

Notokenformat•  Tokenisopaquetotheclient•  Tokenneedstobeissuedbytheauthorizationserverandunderstoodbytheresourceserver,butthey’refreetousewhateverformattheywant

•  However,JSONWebTokens(JWT)provideausefulcommonformat

37

@justin _ _ richer

https://bspk.io/

Nocryptographicmethods•  CoreOAuthreliesonTLSforprotectinginformationintransit

•  However,othermechanismslikeJSONObjectSigningandEncryption(JOSE)definethingsthatcanbeusedwithOAuth

38

@justin _ _ richer

https://bspk.io/

Notasingleprotocol•  OAuth2.0isaframework– Severalcoreflowsplusextensions

•  Twothingscan“implementOAuth”butbeincompatiblewitheachother

•  However,codere-useandpatternsbetweencommoncomponentsmakeslifesimpler

39

@justin _ _ richer

https://bspk.io/

THEAUTHORIZATIONCODEFLOWAdeepdiveintothecanonicalOAuth2.0transaction

40

@justin _ _ richer

https://bspk.io/

ThepiecesofOAuth

Resource Owner

Access Token

Authorization Server

ProtectedResource

Client

41

@justin _ _ richer

https://bspk.io/

Theauthorizationcodeflow

Resource Owner Authorization

Server

ProtectedResource

Client

Resource owner’s credentials

Client’s credentials

Authorization code

Access token

42

@justin _ _ richer

https://bspk.io/

TWOFORMSOFCOMMUNICATION

43

@justin _ _ richer

https://bspk.io/

Thebackchannel

Resource Owner Authorization

Server

ProtectedResource

Client

Back channel uses direct HTTP connections between components,

the browser is not involved

44

@justin _ _ richer

https://bspk.io/

Thefrontchannel

Resource Owner Authorization

Server

ProtectedResource

Client

Front channel uses HTTP redirects through the web browser, no direct connections

45

@justin _ _ richer

https://bspk.io/

Afrontchannelrequest/response

Fron

t Cha

nnel

Re

spon

seFr

ont C

hann

el

Requ

est

Resource Owner

Authorization Server

Client

HTTP Redirect

HTTP Redirect

HTTP Request

HTTP Request

HTTP Response

HTTP Response

46

@justin _ _ richer

https://bspk.io/

Whyboth?•  Separationofinformation•  Frontchannelwhentheuser’sinvolved•  Backchannelwhenthey’renot

47

@justin _ _ richer

https://bspk.io/

THEAUTHORIZATIONCODEFLOWStepbystep

48

@justin _ _ richer

https://bspk.io/

AuthorizationCode:Step1

Resource Owner Authorization

Server

ProtectedResource

Client

Client redirects the resource owner to the authorization server’s authorization endpoint

49

@justin _ _ richer

https://bspk.io/

AuthorizationCode:Step2

Resource Owner Authorization

Server

ProtectedResource

Client

Resource owner authenticates to the authorization server

50

@justin _ _ richer

https://bspk.io/

AuthorizationCode:Step3

Resource Owner Authorization

Server

ProtectedResource

Client

Resource owner authorizes the client

?

51

@justin _ _ richer

https://bspk.io/

AuthorizationCode:Step4

Resource Owner Authorization

Server

ProtectedResource

Client

Authorization server redirects resource owner back to the client with an

authorization code

52

@justin _ _ richer

https://bspk.io/

AuthorizationCode:Step5

Resource Owner Authorization

Server

ProtectedResource

Client

Client sends the authorization code to the authorization

server’s token endpoint

Client authenticates using its own credentials

53

@justin _ _ richer

https://bspk.io/

AuthorizationCode:Step6

Resource Owner Authorization

Server

ProtectedResource

Client

Authorization server issues an OAuth access

token to the client

54

@justin _ _ richer

https://bspk.io/

AuthorizationCode:Step7

Resource Owner Authorization

Server

ProtectedResource

Client

Client accesses the protected resource using

the access token

55

@justin _ _ richer

https://bspk.io/

REFRESHTOKENS

56

@justin _ _ richer

https://bspk.io/

Whentheuserisn’tthere•  Accesstokensworkaftertheuserleaves– OneoftheoriginaldesigngoalsofOAuth

•  Whatdoesaclientdowhentheaccesstokenstopsworking?– Expiration– Revocation

57

@justin _ _ richer

https://bspk.io/

Gettinganewtoken•  Repeattheprocessofgettingatoken–  Interactivegrants:sendtheresourceownertotheauthorizationendpoint

•  Butwhatiftheuser’snotthereanymore?

58

@justin _ _ richer

https://bspk.io/

Refreshtokens•  Issuedalongsidetheaccesstoken•  Usedforgettingnewaccesstokens– Presentedalongwithclientcredentials– Notgoodforcallingprotectedresourcesdirectly

59

@justin _ _ richer

https://bspk.io/

SCOPES

60

@justin _ _ richer

https://bspk.io/

APIDesign•  NaïveAPIs(likewhatwebuilt)allowsimpleyes/noaccess–  Ifyourtokenisgood,yourrequestisgood

•  SmarterAPIsdivideaccess

61

@justin _ _ richer

https://bspk.io/

Limitedaccess•  Typeofaction– Read,write,delete

•  Typeofresource– Photos,metadata,profile

•  Timeofaccess– Userisoffline,limitednumberofaccesses

62

@justin _ _ richer

https://bspk.io/

OAuthScopes•  Stringsthatrepresentwhatthetokencando•  Clientcanaskforscopes•  Resourceownerapprovesscopes•  Accesstokenisboundtoscopes

63

@justin _ _ richer

https://bspk.io/

OTHERWAYSTODOOAUTH2.0

64

@justin _ _ richer

https://bspk.io/

Protocolflexibility•  Canonicalusecase:webserverbasedapplicationaccessedthroughabrowser

•  Authorizationcodeflowisbuiltaroundthisusecase

•  Whataboutdifferentkindsofclients?•  Whataboutdifferentkindsofdelegation?

65

@justin _ _ richer

https://bspk.io/

IMPLICITFLOW

66

@justin _ _ richer

https://bspk.io/

Stufftheclientintothebrowser•  Authorizationcodeflowkeepsthetokenoutofthebrowserandintheclient

•  Butwhatiftheclientisinsidethebrowser?

67

@justin _ _ richer

https://bspk.io/

Theimplicitflow

Resource Owner Authorization

Server

ProtectedResource

Client Inside the Browser

Implicit grant type uses only the front

channel since the client is inside the browser

68

@justin _ _ richer

https://bspk.io/

CLIENTCREDENTIALSFLOW

69

@justin _ _ richer

https://bspk.io/

Clientactsonitsownbehalf•  Noexplicitresourceowner•  ReplacementforAPIkeys

70

@justin _ _ richer

https://bspk.io/

Theclientcredentialsflow

Authorization Server

ProtectedResource

Client

Client credentials grant type: Client trades its own credentials for a

token, uses only the back channel since the client is acting on its own behalf

71

@justin _ _ richer

https://bspk.io/

RESOURCEOWNERPASSWORDFLOW

72

@justin _ _ richer

https://bspk.io/

Stealingthepassword•  Codifytheanti-pattern:asktheuserfortheircredentialsandreplaythem

•  Insteadofsavingthecredentials,tradeforanaccesstoken

73

@justin _ _ richer

https://bspk.io/

Theresourceownerpasswordflow

Resource Owner Authorization

Server

ProtectedResource

Client

?

Resource owner credentials grant type:

Client trades username and password for an OAuth token over the back channel

74

@justin _ _ richer

https://bspk.io/

HOLDON!Didn’twesayitwasbadtostealthecredentials?

75

@justin _ _ richer

https://bspk.io/

ASSERTIONFLOWS

76

@justin _ _ richer

https://bspk.io/

Third-partyauthorization•  Haveatrustedthirdpartyhandauthorizationtotheclient

•  Clienttradesthatforatoken

77

@justin _ _ richer

https://bspk.io/

Theassertionsflows

Authorization Server

Assertion provider

ProtectedResource

Client

Client trades a cryptographically protected element

(assertion) for a token

78

@justin _ _ richer

https://bspk.io/

DEVICEFLOW

79

@justin _ _ richer

https://bspk.io/

Limitedinteractivity•  Noteveryclienthasawebbrowser– Set-topboxes– Smartdevices

•  Howdowegetuserinteraction?– Splitthepieces– Usetheusertocarrytheinformation

80

@justin _ _ richer

https://bspk.io/

ThedeviceflowDevice grant type

gives the resource owner a user code to enter at the authorization server

Resource Owner Authorization

Server

ProtectedResource

Device

Device Code

User Code

Device code is presented in the back

channel

81

@justin _ _ richer

https://bspk.io/

NATIVECLIENTS

82

@justin _ _ richer

https://bspk.io/

What’sanativeclient?•  Runsontheenduser’ssystem– Nothostedonaremotewebserver– Notexecutedinsideofawebbrowser

•  Canbedesktopormobile– Localself-containedwebserverappsqualify

83

@justin _ _ richer

https://bspk.io/

Whatmakesanativeclientdifferent?

•  Functionalitylivesoutsidethebrowser•  Can’tkeepsecretsfromtheuser– Especiallyconfigure-timesecrets

•  RequiresadaptationstoredirectURItousethefrontchannel

84

@justin _ _ richer

https://bspk.io/

Dealingwithsecrets•  Applicationiscopiedandrunmanytimes– Shouldn’tgiveeachcopythesamesecret

•  Dynamicclientregistration– GiveeachinstanceitsownIDandsecret

•  Publicclients– ShareanIDanddon’tusesecrets

85

@justin _ _ richer

https://bspk.io/

RedirectURIs•  CustomURIscheme– myapp:/oauth_callback?code=ABC123

•  Locallyhostedwebserver–  http://localhost:39103/myapp?code=ABC123

•  Remotehostwithpushnotification–  https://push.example.com/app-942/code=ABC123

86

@justin _ _ richer

https://bspk.io/

RedirectURIswithcustomschemes

•  Appsneedtoregisterfornamespace•  Anyappcantakeanynamespace•  MaliciousappscantrytograbitemscominginonredirectURIs– Authorizationcodes(forcodeflow)– Tokens(forimplicitflow)

87

@justin _ _ richer

https://bspk.io/

PKCE:Sendingthechallenge

Resource Owner Authorization

Server

ProtectedResource

Client

Client generates the code verifi er and

challenge, includes the challenge in the front-channel request to the authorization server

88

@justin _ _ richer

https://bspk.io/

PKCE:Sendingtheverifier

Resource Owner Authorization

Server

ProtectedResource

Client

Client sends the verifi er in the back-

channel request to the authorization server

89

@justin _ _ richer

https://bspk.io/

PKCE:Verifyingthechallenge

Resource Owner Authorization

Server

ProtectedResource

Client

Authorization server re-generates the

challenge from the verifi er and compares

it to the challenge previously sent

90

@justin _ _ richer

https://bspk.io/

MANAGINGTHEGRANTTYPES

91

@justin _ _ richer

https://bspk.io/

Differentusecases•  Authorizationcodeflow:webapplications,somenativeapplications

•  Implicitflow:in-browserapplications•  Clientcredentialsflow:non-interactive•  Passwordflow:trustedlegacyclients•  Assertionflows:trustframeworks

92

@justin _ _ richer

https://bspk.io/

HOWTOCHOOSEAGRANTTYPE

93

@justin _ _ richer

https://bspk.io/

94

Can the client display a simple code, image, or

URL to the user?

Is the client acting on behalf of a

resource owner?

Is the client running completely inside of a

web browser?

Is the client a native application?

Yes

YesYes Yes

Yes

Yes

Yes Yes

No

No

NoNo

Can the resource owner interact with a web browser

while using the client?

Does the user have a simple set of credentials

like a password?

Is the client acting on its own behalf?

Authorization Code

Add PKCE or DynReg

Assertion

Resource Owner Credentials

Client Credentials

Implicit

Is the client acting on the authority of a trusted third party?

Choose the appropriate OAuth grant type for

the type of application you’re building

Device

@justin _ _ richer

https://bspk.io/

OpenIDConnect

End User

Session at the Relying Party

Identity Provider

Identity Profi le APIReyling Party(Application)

End User’s Credentials, Authorization of the Relying Party

ID Token and Access Token

Access Token and User Information

95

@justin _ _ richer

https://bspk.io/

DynamicClientRegistration

Resource Owner Authorization

Server

ProtectedResource

Client

Request: Display name, redirect URIs, etc.

Response: Client identifi er, client secret, etc.

96

@justin _ _ richer

https://bspk.io/

Softwarestatements•  Thirdpartygeneratesanassertionthatcontainsfixedattributesoftheclient–  Clientcan’tchangeoroverridewhat’sinthestatement

•  Clientpresentsthestatementalongsideanyvariableattributes

•  ServergeneratesuniqueIDandsecretforclient

97

@justin _ _ richer

https://bspk.io/

Whyuseasoftwarestatement?•  Manyinstancesofaclientsoftware– EachinstanceneedsitsownID/secret– Allinstancesshouldbe“recognizable”

•  Allowpre-registrationacrossdomains– Softwarestatementfromtrustedserver–  IndividualASregistrationsforclients

98

@justin _ _ richer

https://bspk.io/

TOKENINTROSPECTION

99

@justin _ _ richer

https://bspk.io/

OAuthtokensareopaque•  Butthey’reonlyopaquetotheclient•  Protectedresourceneedstoknowthetoken– What’sitgoodfor?– Whoissuedit?–  Isitvalid?

100

@justin _ _ richer

https://bspk.io/

Howdoestheresourceknow?•  Databaselookup– ASandRSareinthesamebox

•  Packinformationintothetokenitself– RememberJWT?

•  QuerytheAS– Runtimelookupoverthenetwork

101

@justin _ _ richer

https://bspk.io/

“What’sthistokengoodfor?”•  ProtectedresourcequeriestheASaboutatokenitreceived

•  ASrespondswithaJSONstructuredescribingthetoken’sstatus

102

@justin _ _ richer

https://bspk.io/

Introspectiontrade-offs•  Requiresextracredentials(attheRS)•  Morenetworktraffic•  Subjecttocacheconsistencyproblems–  Introspecteverytime?Onlyontimeout?

103

@justin _ _ richer

https://bspk.io/

TOKENREVOCATION

104

@justin _ _ richer

https://bspk.io/

Completingthetokenlifecycle•  OAuthdefineshowtogetanewtokenandrefreshadeadtoken

•  Revocationallowsclientstoproactivelythrowawaytokenstheynolongeruse

105

@justin _ _ richer

https://bspk.io/

Whyrevoketokens?•  Nativeapplicationbeinguninstalled•  Userselects“logout”or“de-authorize”fromtheclient(nottheAS)

106

@justin _ _ richer

https://bspk.io/

Asimpleprotocol•  ClientPOSTstotherevocationendpoint–  Tokenincludedinbody

•  Serverdeletesthetokenifitfindsit•  ServertellstheclienteverythingisOK–  Evenifnotokenwasdeleted,wepretendwedid– Otherwiseclientscouldusethistofishfortokenvalues

•  Clientthrowsoutitscopyofthetoken107

@justin _ _ richer

https://bspk.io/

POP,MTLS,ANDTOKENBINDING

108

@justin _ _ richer

https://bspk.io/

Beyondbearertokens•  Bearertokensaresentas-isoverthewire•  Anyonewhohasaccesstothetokencanuseit•  ProofofPossession(PoP)tokensrequirecryptographicproofofakey– Tokenistransmittedas-is– Keyisusedtosignsomething,nottransmitteditself

109

@justin _ _ richer

https://bspk.io/

Twoparts

Token:Opaque to client

Associated with scopes and ROSent as-is to PR

Key:Known to client

Associated with tokenUsed to sign request

110

@justin _ _ richer

https://bspk.io/

MutualTLS•  Clientpresentscertificatetotokenendpoint•  AShashescertificateandtiesittotoken•  ClientpresentssamecertificatetoRS•  RShashescertificateandseesifit’sthesameastheoneboundtothetoken

•  ClientdoesnothavetoauthenticatewithTLS

111

@justin _ _ richer

https://bspk.io/

Tokenbinding

Resource Owner Authorization

Server

Use TLS Channel ABC

Here’s a cookie, only use it on TLS

Channel ABC

Here’s that cookie again, this is TLS

Channel ABC

112

@justin _ _ richer

https://bspk.io/

Aproblemwithtokenbinding

Resource Owner Authorization

Server

ProtectedResource

Client

1

2 3

4

5

113

@justin _ _ richer

https://bspk.io/

WRAPPINGUP

114

@justin _ _ richer

https://bspk.io/

SURVEY!https://www.surveymonkey.com/r/101introOAuth

115

@justin _ _ richer

https://bspk.io/

THANKYOUhttp://oauthinaction.com/

116