Switching Apigee Management Endpoint to OAuth

on Monday, June 11, 2018

So, Apigee is updating their REST Management API to no longer accept Basic Authorization headers and instead use OAuth tokens. It’s a good move, as it adds a little more security by issuing tokens that are only valid for 30 minutes. The strength of the security is still provided through HTTPS connections and proper storage of credentials by the users/clients.

To make the update, I’ll be switching out a Basic Authentication setup with an OAuth setup. Apigee decided to go with a password grant, with the optional parameters not used. It’s a bit interesting that they went with a password grant over a client credentials grant. The client credential grant seems a lot more straight forward, and it would fit the scenario that each end user/client can directly use their credentials to create automated tooling.

By using a password grant it would imply that they couldn’t use their Apigee Server as the real OAuth server. Their OAuth server is at https://login.apigee.com, and it must be the endpoint that provides SSO protection for https://apigee.com/edge (the Management Website / Management UI). And, any users within that OAuth server must be provisioned into the REST Management API system (https://api.enterprise.apigee.com) as “Applications”. But that’s all speculation.

However they do it, converting for Basic Authentication to OAuth is pretty straight forward. The trickiest part is adding retry logic to the calls that fails because an access token has expired. We just need to add code to detect the 401 Unauthorized response, ask for a new token and then retry the call.

Basic Authentication (Before):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
########### Apigee.psm1
 
# bump up TLS to 1.2
[System.Net.ServicePointManager]::SecurityProtocol =
                [System.Net.SecurityProtocolType]::Tls12 + [System.Net.SecurityProtocolType]::Tls11 + [System.Net.SecurityProtocolType]::Tls;
 
$global:Apigee = @{}
 
# get username / password for management API
import-module secretserver
$secret = get-secretserversecret -filter "apigee - admin account"
 
$global:Apigee.username = $secret.username
$global:Apigee.password = $secret.password
 
$combo = $global:apigee.username + ":" + $global:apigee.password
$plaintextbytes = [system.text.encoding]::utf8.getbytes($combo)
$base64encoded = [system.convert]::tobase64string($plaintextbytes)
$basicauth = "basic $base64encoded"
$global:apigee.authheader = @{ authorization = $basicauth }
 
 
# grab functions from files (from C:\Chocolatey\chocolateyinstall\helpers\chocolateyInstaller.psm1)
Resolve-Path $root\Apigee.*.ps1 |
    ? { -not ($_.ProviderPath.Contains(".Tests.")) } |
    % { . $_.ProviderPath; }
 
 
 
 
########### Apigee.Rest.ps1
 
Function Invoke-ApigeeRest {
[CmdletBinding()]
Param (
    [Parameter(Mandatory = $true)]
    [string] $ApiPath,
    [ValidateSet("Default","Delete","Get","Head","Merge","Options","Patch","Post","Put","Trace")]
    [string] $Method = "Default",
    [object] $Body = $null,
    [string] $ContentType = "application/json",
    [string] $OutFile = $null
)
 
    if($ApiPath.StartsWith("/") -eq $false) {
        $ApiPath = "/$ApiPath"
    }
    $Uri = $global:Apigee.ApiUrl + $ApiPath
    
    if($Body -eq $null) {
        $result = Invoke-RestMethod `
                    -Uri $Uri `
                    -Method $Method `
                    -Headers $global:Apigee.AuthHeader `
                    -ContentType $ContentType `
                    -OutFile $OutFile
    } else {
        $result = Invoke-RestMethod `
                    -Uri $Uri `
                    -Method $Method `
                    -Headers $global:Apigee.AuthHeader `
                    -Body $Body `
                    -ContentType $ContentType `
                    -OutFile $OutFile
    }
     
    return $result
}
 
 
[string[]]$funcs =
    "Invoke-ApigeeRest"
 
Export-ModuleMember -Function $funcs

OAuth Authentication (After):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
########### Apigee.psm1
 
 
# bump up TLS to 1.2
[System.Net.ServicePointManager]::SecurityProtocol =
                [System.Net.SecurityProtocolType]::Tls12 + [System.Net.SecurityProtocolType]::Tls11 + [System.Net.SecurityProtocolType]::Tls;
 
$global:Apigee = @{}
 
# get username / password for management API
import-module secretserver
$secret = get-secretserversecret -filter "apigee - admin account"
 
$global:Apigee.username = $secret.username
$global:Apigee.password = $secret.password
 
 
# Use OAuth for access credentials. All public info here:
$global:Apigee.OAuthLogin = @{}
$global:Apigee.OAuthLogin.Method = "POST"
$global:Apigee.OAuthLogin.Url = "https://login.apigee.com/oauth/token"
$global:Apigee.OAuthLogin.ContentType = "application/x-www-form-urlencoded"
$global:Apigee.OAuthLogin.Headers = @{
                                                Accept = "application/json;charset=utf-8"
                                                Authorization = "Basic ZWRnZWNsaTplZGdlY2xpc2VjcmV0"
                                            }
$global:Apigee.OAuthLogin.Body = @{
                                                username = $global:Apigee.Username
                                                password = $global:Apigee.Password
                                                grant_type = "password"
                                        }
$global:Apigee.OAuthLogin.ResultObjectName = "ApigeeAccessToken"
# $global:Apigee.OAuthToken set below
# $global:Apigee.AuthHeader set in Apigee.Login.ps1
 
# grab functions from files (from C:\Chocolatey\chocolateyinstall\helpers\chocolateyInstaller.psm1)
Resolve-Path $root\Apigee.*.ps1 |
    ? { -not ($_.ProviderPath.Contains(".Tests.")) } |
    % { . $_.ProviderPath; }
 
# get authorization token
$global:Apigee.OAuthToken = Get-ApigeeAccessTokens
 
 
 
 
 
 
 
########### Apigee.Login.psm1
 
 
 
<#
.SYNOPSIS
    Makes a call to the Apigee OAuth login endpoint and gets access tokens to use.
 
    This should be used internally by the Apigee module. But, it shouldn't be needed by
    the developer.
 
 
.EXAMPLE
    $result = Get-ApigeeAccessTokens
#>
Function Get-ApigeeAccessTokens {
[CmdletBinding()]
[OutputType([PSCustomObject])]
Param ()
 
    $results Invoke-WebRequest `
                    -Uri $global:Apigee.OAuthLogin.Url `
                    -Method $global:Apigee.OAuthLogin.Method `
                    -Headers $global:Apigee.OAuthLogin.Headers `
                    -ContentType $global:Apigee.OAuthLogin.ContentType `
                    -Body $global:Apigee.OAuthLogin.Body
 
    if($results.StatusCode -ne 200) {
        $resultsAsString = $results | Out-String
        throw "Authentication with Apigee's OAuth Failed. `r`n`r`nFull Response Object:`r`n$resultsAsString"
    }
     
    $resultsObj = ConvertFrom-Json -InputObject $results.Content
    $resultsObj = Add-PsType -PSObject $resultsObj -PsType $global:Apigee.OAuthLogin.ResultObjectName
 
    Set-ApigeeAuthHeader -Authorization $resultsObj.access_token
 
    return $resultsObj
}
 
<#
.SYNOPSIS
    Sets $global:Apigee.AuthHeader @{ Authorization = "value passed in" }
 
    This is used to authenticate all calls to the Apigee REST Management endpoints.
 
.EXAMPLE
    Set-ApigeeAuthHeader -Authorization "Bearer ..."
#>
Function Set-ApigeeAuthHeader {
[CmdletBinding()]
[OutputType([PSCustomObject])]
Param (
    [Parameter(Mandatory = $true)]
    $Authorization
)
 
    $bearerAuth = "Bearer $Authorization"
    $global:Apigee.AuthHeader = @{ Authorization = $bearerAuth }
}
 
 
 
 
[string[]]$funcs =
    "Get-ApigeeAccessTokens", "Set-ApigeeAuthHeader"
 
Export-ModuleMember -Function $funcs
 
 
 
 
 
 
 
########### Apigee.Rest.psm1
 
 
Function Invoke-ApigeeRest {
[CmdletBinding()]
Param (
    [Parameter(Mandatory = $true)]
    [string] $ApiPath,
    [ValidateSet("Default","Delete","Get","Head","Merge","Options","Patch","Post","Put","Trace")]
    [string] $Method = "Default",
    [object] $Body = $null,
    [string] $ContentType = "application/json",
    [string] $OutFile = $null
)
 
    if($ApiPath.StartsWith("/") -eq $false) {
        $ApiPath = "/$ApiPath"
    }
    $Uri = $global:Apigee.ApiUrl + $ApiPath
 
    $attempt = 1
    $retry = $false
         
    do {
        $retry = $false
 
        try {
 
            if($Body -eq $null) {
                $result = Invoke-RestMethod `
                            -Uri $Uri `
                            -Method $Method `
                            -Headers $global:Apigee.AuthHeader `
                            -ContentType $ContentType `
                            -OutFile $OutFile
            } else {
                $result = Invoke-RestMethod `
                            -Uri $Uri `
                            -Method $Method `
                            -Headers $global:Apigee.AuthHeader `
                            -Body $Body `
                            -ContentType $ContentType `
                            -OutFile $OutFile
            }
 
        } catch {
 
            # if the request is unauthorized, get a new access tokens & retry
            $isWebException = (Get-PsType -PSObject $_.Exception) -eq "System.Net.WebException"
            $is401Unauthorized = $_.Exception.Message -eq "The remote server returned an error: (401) Unauthorized."
             
            if($isWebException -and $is401Unauthorized) {
                Get-ApigeeAccessTokens
                 
                if($attempt -lt 2) {
                    $retry = $true
                }
            } else {
 
                throw    # unexpected exception, so rethrow
 
            }
        }
 
        $attempt++
 
    } while( $retry )
     
    return $result
}
 
 
[string[]]$funcs =
    "Invoke-ApigeeRest"
 
Export-ModuleMember -Function $funcs


Creative Commons License
This site uses Alex Gorbatchev's SyntaxHighlighter, and hosted by herdingcode.com's Jon Galloway.