Skip to Content

This is a continuation of Part 2 (https://blogs.sap.com/2017/07/14/implement-facebook-login-on-sap-api-management-part-2/)

 

Building the Redirect URI Proxy

This proxy will be the target of the redirect from Facebook after login and approve/reject of the requested authorizations by the resource owner (i.e. the facebook user). In case of a success (the only case we are going to implement) the redirect will contain the authorization code and the previously assigned value for state.These two values will serve as basic inputs to acquire the token and internalize it (convert it into an API Management token).

 

To do this, we open the API Portal. In the Develop Screen we create a new API Proxy. The API Proxy should get the following parameters:

Parameter Parameter Value
API Provider NONE
URL any url
Name FacebookRedirect
Title FacebookRedirect
Description FacebookRedirect
Host Alias no preference
API Base Path /v1/login/facebook/redirect (path must be registered with facebook)
Service Type REST

Now press “save as draft”. Then go to “Advanced Properties” and change the Route Rules to point to “NONE”. This will create a loopback broxy.

Now go to policies. In the policies we check for an API Key as the first step of the PreFlow of the Proxy Endpoint:

<VerifyAPIKey async='true' continueOnError='false' enabled='true' 
xmlns='http://www.sap.com/apimgmt'>
	<APIKey ref='request.queryparam.state'/>
</VerifyAPIKey>

This will check whether the state parameter is a valid API Key. In our example this is the only client authentication we use. Per OAuth Specification this is not enough (but this is for demo purposes only), hence change it in a productive flow.

After that we can exchange the facebook Authorization Code for a token. This will be done using a Service Callout:

<ServiceCallout async="true" continueOnError="false" enabled="true" xmlns="http://www.sap.com/apimgmt">
	<Request>
	    <Set>      
		<QueryParams>        
			<QueryParam name="client_id">"Facebok Client ID"</QueryParam>   
			<QueryParam name="redirect_uri">"Facebook redirect URI (i.e. this API Proxy)"</QueryParam> 
			<QueryParam name="client_secret">"Facebook Client Secret"</QueryParam>
			<QueryParam name="code">{request.queryparam.code}</QueryParam>
		</QueryParams>      
		<Verb>GET</Verb>      
		</Set>  
	    
	</Request>
	<Response>sapapim.facebook.token.response</Response>
	<Timeout>30000</Timeout>
	<HTTPTargetConnection>
		<URL>https://graph.facebook.com/v2.9/oauth/access_token</URL>
	</HTTPTargetConnection>
</ServiceCallout>

Here all three values from part 1 are needed:

  1. Redirect URI
  2. App ID
  3. App Secret

Now the facebook response needs to be processed. This is done in an Extract Variables Policy:

<ExtractVariables async="true" continueOnError="false" enabled="true" xmlns='http://www.sap.com/apimgmt'>
	<JSONPayload>   
		<Variable name="sapapim.fblogin.token" type="string">      
			<JSONPath>$.access_token</JSONPath>   
		</Variable> 
		<Variable name="sapapim.fblogin.expires_in" type="string">      
			<JSONPath>$.expires_in</JSONPath>   
		</Variable> 
	</JSONPayload>  
	<Source>sapapim.facebook.token.response.content</Source>
</ExtractVariables> 

After that we do another Service Callout using the facebook token. This callout will retrieve information about the logged in user. Later we will persist this information as token attributes. Attribute values will be injected into the flow whenever the access token is verified and hence can be used to manage the user context:

<ServiceCallout async="true" continueOnError="false" enabled="true" xmlns="http://www.sap.com/apimgmt">
	<Request>
	    <Set>
	        <Headers>        
			    <Header name="Authorization">Bearer {sapapim.fblogin.token}</Header>
		    </Headers>      
		    <QueryParams>        
			    <QueryParam name="fields">email,first_name,last_name,gender,name</QueryParam>
		    </QueryParams> 
		    <Verb>GET</Verb>      
		</Set>  
	    
	</Request>
	<Response>sapapim.facebook.personinfo.response</Response>
	<Timeout>30000</Timeout>
	<HTTPTargetConnection>
		<URL>https://graph.facebook.com/v2.9/me</URL>
	</HTTPTargetConnection>
</ServiceCallout>

In order to access the logged in user’s values the Graph API provides the “me” resource. This is very handy fur call like this. The previously acquired token will be transferred in an Authorization header. The requested attributes are managed in the “fields” query parameter.

Again the response data is extracted using an Extract Variables Policy:

<ExtractVariables async="true" continueOnError="false" enabled="true" xmlns='http://www.sap.com/apimgmt'>
	<JSONPayload>   
		<Variable name="sapapim.fblogin.email" type="string">      
			<JSONPath>$.email</JSONPath>   
		</Variable>
		<Variable name="sapapim.fblogin.first_name" type="string">      
			<JSONPath>$.first_name</JSONPath>   
		</Variable>
		<Variable name="sapapim.fblogin.last_name" type="string">      
			<JSONPath>$.last_name</JSONPath>   
		</Variable>
		<Variable name="sapapim.fblogin.gender" type="string">      
			<JSONPath>$.gender</JSONPath>   
		</Variable>
		<Variable name="sapapim.fblogin.name" type="string">      
			<JSONPath>$.name</JSONPath>   
		</Variable>
		<Variable name="sapapim.fblogin.id" type="string">      
			<JSONPath>$.id</JSONPath>   
		</Variable>
	</JSONPayload>  
	<Source>sapapim.facebook.personinfo.response.content</Source>
</ExtractVariables> 

With this we have now acquired the facebook token and used it to retrieve meta information about the logged-in user. Now we need to convert the facebook token into an SAP API Management token which can be verified in any other API Proxy.

To do this we need to set a couple of variables and parameters. This is done using an Assign Message Policy.

<AssignMessage async="false" continueOnError="false" enabled="true" xmlns='http://www.sap.com/apimgmt'>
 
    <Set>   
          <FormParams>      
			<FormParam name="client_id">{request.queryparam.state}</FormParam>
			<FormParam name="grant_type">client_credentials</FormParam>
          </FormParams>   
	</Set> 
	<AssignVariable>   
          <Name>oauth_external_authorization_status</Name>   
          <Value>true</Value>                              
     </AssignVariable> 
	
	<IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
	<AssignTo createNew="false" type="request">request</AssignTo>
</AssignMessage>

First of all we set the variable “oauth_external_authorization_status”. This is needed to tell the subsequent OAuth Policy that it must not take care of authenticating the client. Remember we did this in the very first Verify API Key Policy (although in a productive environment this would not be sufficient, for our demo this is enough). Furthermore we write grant type and client id (API Key) to the default locations expected by the OAuth 2 policy. Now comes the OAuth Policy. This policy will “internalize” the facebook token and persist the acquired information as attributes:

<OAuthV2 async="false" continueOnError="false" enabled="true" xmlns="http://www.sap.com/apimgmt">
   <AppEndUser>sapapim.fblogin.id</AppEndUser>
   <Attributes>
       <Attribute display="true" name="email" ref="sapapim.fblogin.email"></Attribute>
       <Attribute display="true" name="first_name" ref="sapapim.fblogin.first_name"></Attribute>
       <Attribute display="true" name="last_name" ref="sapapim.fblogin.last_name"></Attribute>
       <Attribute display="true" name="gender" ref="sapapim.fblogin.gender"></Attribute>
       <Attribute display="true" name="name" ref="sapapim.fblogin.name"></Attribute>
       <Attribute display="true" name="facebook_id" ref="sapapim.fblogin.id"></Attribute>
   </Attributes>
   <ExpiresIn ref="sapapim.fblogin.expires_in">1</ExpiresIn>
   <ExternalAccessToken>sapapim.fblogin.token</ExternalAccessToken>
   <ExternalAuthorization>true</ExternalAuthorization>
   <Operation>GenerateAccessToken</Operation>
   <GenerateResponse enabled="true"/>
   <StoreToken>true</StoreToken>
   <SupportedGrantTypes>
    <GrantType>authorization_code</GrantType>
    <GrantType>client_credentials</GrantType>
    <GrantType>implicit</GrantType>
    <GrantType>password</GrantType>
</SupportedGrantTypes>
</OAuthV2>
 

 

Now your API Proxy (Proxy Endpoint Preflow) should look as follows:

 

Now you can again invoke:

https://<apimanagementruntime>:443/v1/login/facebook/oauth?apikey=<api key from dev portal>

If everything is setup correctly, you will be redirected to your newly created proxy, which will respond with the following JSON:

{
refresh_token_expires_in: "0",
api_product_list: "Your Product",
app_enduser: "Facebook user ID",
gender: "gender",
last_name: "Last Name",
organization_name: "your org",
developer.email: "...",
token_type: "BearerToken",
issued_at: "1499684634649",
client_id: "API Key",
facebook_id: "..Facebook user ID",
access_token: "Facebook token",
application_name: "...",
scope: "Scope Set on Product",
name: "First- & Lastname",
expires_in: "...",
first_name: "Firstname",
refresh_count: "0",
email: "e-mail",
status: "approved"
}

With this you are officially logged in. The returned token can now be used for native calls to facebook’s Grahp API as well as for calls to API Management:

Here are a few points which underpin why I think that this is not yet production grade:

  1. No proper OAuth grant type implementation on API Management (it is a freestyle to be simple)
  2. No secure client authentication is implemented
  3. State parameter is not used as intended (which is to prevent XSRF attacks), even worse, it discloses the API key
  4. User information is not persisted anywhere and hence can’t be used to authorize on an object instance (e.g. Business Partner 4711, Account 7047, …) level

Nevertheless I hope it is fun to reproduce and provides some insights on how to deal with external tokens.

To report this post you need to login first.

Be the first to leave a comment

You must be Logged on to comment or reply to a post.

Leave a Reply