Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OAuth Copernicus API #563

Closed
amael-ls opened this issue Oct 10, 2024 · 5 comments
Closed

OAuth Copernicus API #563

amael-ls opened this issue Oct 10, 2024 · 5 comments

Comments

@amael-ls
Copy link

Hello,

I am trying to connect to the Copernicus Land Monitoring Service API (see stack overflow question for more details on R code)

When I use

library(httr2)

## Read the keys created at step 2.1
service_key = rjson::fromJSON(file = "./land-copernicus.json")
private_key = service_key[["private_key"]]

## Create the authorisation grant from private key
claim = jwt_claim(
	iss = service_key[["client_id"]],
	sub = service_key[["user_id"]],
	aud = service_key[["token_uri"]],
	iat = Sys.time(),
	exp = Sys.time() + 60*60 # Gives an expiration date of one hour (the maximum authorised)
)

grant = jwt_encode_hmac(claim = claim, secret = private_key, header = list(alg = "RS256"))

## Exchange the JWT authorization grant at the @@oauth2-token endpoint
req = request(base_url = service_key[["token_uri"]]) |>
	req_headers("Accept" = "application/json", "Content-Type" = "application/x-www-form-urlencoded") |>
	req_body_json(data = list(grant_type = "urn:ietf:params:oauth:grant-type:jwt-bearer", assertion = grant)) |>
	req_perform(verbosity = 2)

req2 = request(base_url = service_key[["token_uri"]]) |>
	req_headers("Accept" = "application/json", "Content-Type" = "application/x-www-form-urlencoded") |>
	req_body_raw(body = paste0("grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=", grant)) |>
	req_perform(verbosity = 2)

I get errors in both cases. With req, I get:

> POST /@@oauth2-token HTTP/2
-> Host: land.copernicus.eu
-> user-agent: httr2/1.0.5 r-curl/5.2.3 libcurl/7.81.0
-> accept-encoding: deflate, gzip, br, zstd
-> accept: application/json
-> content-type: application/x-www-form-urlencoded
-> content-length: 461
-> 
>> {"grant_type":"urn:ietf:params:oauth:grant-type:jwt-bearer","assertion":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImFsZy4xIjoiUlMyNTYifQ.eyJpc3MiOiIyYjhlMTdmYzBkOTg4YzVkNGY2OTg1ZTMyYjdmY2VkNCIsInN1YiI6Im4wMGg3ZGthIiwiYXVkIjoiaHR0cHM6Ly9sYW5kLmNvcGVybmljdXMuZXUvQEBvYXV0aDItdG9rZW4iLCJleHAiOjE3Mjg1NTQxMTcsIm5iZiI6MTcyODU1MDUxNywiaWF0IjoxNzI4NTUwNTE3LCJqdGkiOiJxdjJ3aWdIM0I3SndqMF9wd0xBazZRSVFmS1Y5Y3VmUkxEYW5QdkJfSVBrIn0.Yr3EMPhvs9K0slvnMKq0mslmEcOoafU4iAW-SIE5Yw4"}
<- HTTP/2 400 
<- cache-control: no-store
<- content-length: 73
<- content-type: application/json
<- date: Thu, 10 Oct 2024 08:56:47 GMT
<- pragma: no-cache
<- server: waitress
<- via: waitress
<- x-frame-options: SAMEORIGIN
<- x-powered-by: Zope (www.zope.dev), Python (www.python.org)
<- strict-transport-security: max-age=31536000; includeSubDomains; preload
<- x-content-type-options: nosniff
<- x-xss-protection: 1; mode=block
<- access-control-allow-headers: Authorization, Content-Type, content-type
<- vary: Origin
<- 
<< **{"error": "invalid_request", "error_description": "Missing 'grant_type'"}**
Error in `req_perform()`:
! HTTP 400 Bad Request.

and with req2:

-> POST /@@oauth2-token HTTP/2
-> Host: land.copernicus.eu
-> user-agent: httr2/1.0.5 r-curl/5.2.3 libcurl/7.81.0
-> accept-encoding: deflate, gzip, br, zstd
-> accept: application/json
-> content-type: application/x-www-form-urlencoded
-> content-length: 451
-> 
>> grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImFsZy4xIjoiUlMyNTYifQ.eyJpc3MiOiIyYjhlMTdmYzBkOTg4YzVkNGY2OTg1ZTMyYjdmY2VkNCIsInN1YiI6Im4wMGg3ZGthIiwiYXVkIjoiaHR0cHM6Ly9sYW5kLmNvcGVybmljdXMuZXUvQEBvYXV0aDItdG9rZW4iLCJleHAiOjE3Mjg1NTQxMTcsIm5iZiI6MTcyODU1MDUxNywiaWF0IjoxNzI4NTUwNTE3LCJqdGkiOiJxdjJ3aWdIM0I3SndqMF9wd0xBazZRSVFmS1Y5Y3VmUkxEYW5QdkJfSVBrIn0.Yr3EMPhvs9K0slvnMKq0mslmEcOoafU4iAW-SIE5Yw4
<- HTTP/2 400 
<- cache-control: no-store
<- content-length: 96
<- content-type: application/json
<- date: Thu, 10 Oct 2024 08:58:42 GMT
<- pragma: no-cache
<- server: waitress
<- via: waitress
<- x-frame-options: SAMEORIGIN
<- x-powered-by: Zope (www.zope.dev), Python (www.python.org)
<- strict-transport-security: max-age=31536000; includeSubDomains; preload
<- x-content-type-options: nosniff
<- x-xss-protection: 1; mode=block
<- access-control-allow-headers: Authorization, Content-Type, content-type
<- vary: Origin
<- 
**<< {"error": "invalid_request", "error_description": "Only RS256 signature algorithm is supported"}**

I do not understand given that I put both agrant_type and the algorithm RS256.

I generated the grant with Python with the following code, copy-pasted the grant in R, and it worked:

import json
import jwt
import time
import requests

base_url = 'https://land.copernicus.eu'

# Load saved key from filesystem
service_key = json.load(open('./land-copernicus.json', 'rb'))

private_key = service_key['private_key'].encode('utf-8')

claim_set = {
    "iss": service_key['client_id'],
    "sub": service_key['user_id'],
    "aud": service_key['token_uri'],
    "iat": int(time.time()),
    "exp": int(time.time() + (60 * 60)),
}
grant = jwt.encode(claim_set, private_key, algorithm='RS256')

Any idea of where could be the problem? Many thanks

@jeroen jeroen transferred this issue from r-lib/jose Oct 10, 2024
@jeroen
Copy link
Member

jeroen commented Oct 10, 2024

Are you sure you are supposed to set "Content-Type" = "application/x-www-form-urlencoded" ? That seems wrong?

@hadley
Copy link
Member

hadley commented Oct 10, 2024

And is there a reason you're not using req_oauth_bearer_jwt() directly?

@amael-ls
Copy link
Author

@jeroen Yes, in their example, they set "Content-Type" = "application/x-www-form-urlencoded"

@jeroen
Copy link
Member

jeroen commented Oct 10, 2024

To mimic the python example, you need to use jwt_encode_sig() and req_body_form(). This works:

library(httr2)

## Read the keys created at step 2.1
service_key = jsonlite::read_json("./land-copernicus.json")
private_key = service_key[["private_key"]]

## Create the authorisation grant from private key
claim = jwt_claim(
  iss = service_key[["client_id"]],
  sub = service_key[["user_id"]],
  aud = service_key[["token_uri"]],
  iat = Sys.time(),
  exp = Sys.time() + 60*60 # Gives an expiration date of one hour (the maximum authorised)
)

grant = jose::jwt_encode_sig(claim, private_key)
req = request(base_url = service_key[["token_uri"]]) |>
  req_body_form(grant_type = "urn:ietf:params:oauth:grant-type:jwt-bearer", assertion = grant) |>
  req_perform(verbosity = 2)

resp_body_json(req)

Not sure if this is the same as req_oauth_bearer_jwt() though

@jeroen jeroen closed this as completed Oct 10, 2024
@amael-ls
Copy link
Author

amael-ls commented Oct 11, 2024

Many thanks, that worked! I just add the reference #522 as it could be an example with a free API and an OAuth

@amael-ls amael-ls changed the title RS256 algorithm OAuth Copernicus API Oct 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants