I am currently using AWS Cognito for user authentication. However, I have plans to make AuthN IDP agnostic. For example, I want to be able to replace Cognito with some other identity provider later.
Currently, I use boto3 cognito API to get idToken like below. How can I write a non-cognito specific implementation to get idTokens so that when the provider changes, I won't be relying on cognito? I want to use some generic python OAuth APIs to obtain idToken for the user. How can I do that?
cognito_client = boto3.client('cognito-idp')
response_tokens = cognito_client.initiate_auth(
AuthFlow='USER_PASSWORD_AUTH',
AuthParameters={
'USERNAME': uname,
'PASSWORD': pwd
},
ClientId=OAUTH_AUDIENCE
)
To get ID token from Cognito you need to call a specific API. And if you want to switch to other IDP later, then you need to use other API provided by that IDP. So, APIs are specific to the IDP.
Hence, as at this moment you want token from Cognito, you need to use AWS APIs.
That said, in case by "different IDP" you mean to say "configuring different IDP with Cognito" then I would like to add that, OAuth 2.0 flow can not be made using CLI or SDKs. Cause, OAuth 2.0 flow is a browser based flow and to login with different IDP you need to use browser.
Related
I am looking for collect data from Google ADS API into GCP by using Python scripts and it requires to fill these items for authentication in the google-ads.yaml file:
developer_token:
client_id:
client_secret:
refresh_token:
login_customer_id:
I was able to fill these items by asking people in my company or generating it with google python scripts in GitHub but I need to understand the role of each, the docs seems to be disperse with a long learning path.
You can follow this guidebook to make your google-ads.yaml file. And for the sample role you provided, below are the definitions of each but you can check this sample template for more details about it.
Developer token
A developer token is required when making requests to the Google Ads API regardless of whether you're using the OAuth2 or Service Account configurations. To obtain a developer token see: https://developers.google.com/google-ads/api/docs/first-call/dev-token
developer_token: INSERT_DEVELOPER_TOKEN_HERE
OAuth2 configuration
The below configuration parameters are used to authenticate using the recommended OAuth2 flow. For more information on authenticating with OAuth2 see:
https://developers.google.com/google-ads/api/docs/oauth/overview
client_id: INSERT_OAUTH2_CLIENT_ID_HERE
client_secret: INSERT_OAUTH2_CLIENT_SECRET_HERE
refresh_token: INSERT_REFRESH_TOKEN_HERE
Login customer ID configuration
Required for manager accounts only: Specify the login customer ID used to authenticate API calls. This will be the customer ID of the authenticated manager account. It should be set without dashes, for example: 1234567890 instead of 123-456-7890. You can also specify this later in code if your application uses multiple manager account + OAuth pairs.
login_customer_id: INSERT_LOGIN_CUSTOMER_ID_HERE
For over a year, I have connected to Salesforce using the simple_salesforce package in order to pull some data from various objects and load it to a data lake.
I have used the authentication method using username / password / security token.
client = Salesforce(
username="****************",
password="*************",
security_token="****************"
)
On the 1st of February came the enforcement of multi factor auth. Starting on that day, I consistently hit the same error over and over.
[{'message': 'This session is not valid for use with the REST API', 'errorCode': 'INVALID_SESSION_ID'}]
After some research, I tried to add a permission set with API Enabled and then API Only user. Result: still the same error, but now I am locked out of the UI.
Has anyone else encountered similar issues and could point me towards the right resources, please? Thanks!
MFA shouldn't matter for API access according to https://help.salesforce.com/s/articleView?id=000352937&type=1 (Ctrl+F "API"), it's probably something else your admin did.
Username, password+token sounds like you're use SOAP login method.
See if you can create a "connected app" in SF to use the OAuth2 login method, more natural for REST API. I wrote a bit about it in https://stackoverflow.com/a/62694002/313628. In the connected app you should be able to allow API access, even full if needed. No idea if Simple has natural place for the keys though, it's bit rubbish if you'll have to craft raw http requests yourself.
Simple's documentation also mentions using JWT to log in (and that requires connected app anyway), basically instead of username + pass you go username + certificate + the fact admin preauthorised this user... You'll be fine until certificate expires.
The text part of https://gist.github.com/booleangate/30d345ecf0617db0ea19c54c7a44d06f can help you with the connected app creation; sample code's probably not needed if you're going with Simple
Goal:
I have set up a Google API Gateway. The backend for the API is a cloud function (written in python). The cloud function should query data from Google BigQuery (BQ). To do that, I want to create a BQ Client (google.cloud.bigquery.Client()). The API should be accessed by different applications using different service accounts. The service accounts have permission to access only specific datasets within my project. Therefore, the service accounts/applications should only be able to query the datasets they have the permission for. Therefore, the BQ Client within the cloud function should be initialized with the service account that sends the request to the API.
What I tried:
The API is secured with the following OpenAPI definition so that a JWT signed by the service account SA-EMAIL is required to send a request there:
securityDefinitions:
sec-def1:
authorizationUrl: ""
flow: "implicit"
type: "oauth2"
x-google-issuer: "SA-EMAIL"
x-google-jwks_uri: "https://www.googleapis.com/robot/v1/metadata/x509/SA-EMAIL"
x-google-audiences: "SERVICE"
For the path that uses my cloud function, I use the following backend configuration:
x-google-backend:
address: https://PROJECT-ID.cloudfunctions.net/CLOUD-FUNCTION
path_translation: CONSTANT_ADDRESS
So in the cloud function itself I get the forwarded JWT as X-Forwarded-Authorization and also the already verified base64url encoded JWT payload as X-Apigateway-Api-Userinfo from the API Gateway.
I tried to use the JWT from X-Forwarded-Authorization to obtain credentials:
bearer_token = request.headers.get('X-Forwarded-Authorization')
token = bearer_token.split(" ")[1]
cred = google.auth.credentials.Credentials(token)
At first, this seems to work since cred.valid returns True, but when trying to create the client with google.cloud.bigquery.Client(credentials=cred) it returns the following error in the logs:
google.auth.exceptions.RefreshError: The credentials do not contain
the necessary fields need to refresh the access token. You must
specify refresh_token, token_uri, client_id, and client_secret.
I do not have much experience with auth/oauth at all, but I think I do not have the necessary tokens/attributes the error is saying are missing available in my cloud function.
Also, I am not exactly sure why there is a RefreshError, since I don't want to refresh the token (and don't do so explicitly) and just use it again (might be bad practice?).
Question:
Is it possible to achieve my goal in the way I have tried or in any other way?
Your goal is to catch the credential that called the API Gateway, and to reuse it in your Cloud Functions to call BigQuery.
Sadly, you can't. Why? Because API Gateway prevent you to achieve that (and it's a good news for security reason). The JWT token is correctly forwarded to your Cloud Functions, but the signature part has been removed (you receive only the header and the body of the JWT token).
The security verification has been done by API Gateway and you have to rely on that authentication.
What's the solution?
My solution is the following: In the truncated JWT that you receive, you can get the body and get the Service Account email. From there, you can use the Cloud Functions service account, to impersonate the Service Account email that you receive.
Like that, the Cloud Functions service account only needs the permission to impersonate these service account, and you keep the permission provided on the original service account.
I don't see other solutions to solve your issue.
The JWT that you are receiving from API Gateway is not an OAuth Access Token. Therefore the JWT Payload portion is not a credential that you can use for the BigQuery Client authorization.
As #guillaume blaquiere pointed out, the Payload contains the email address of an identity. If the identity is a service account, you could implement impersonation of that identity. This could be a good solution if you are using multiple identities with API Gateway. If the identity is a user account, then you would need to implement Domain-Wide Delegation.
I recommend simply using the service account assigned to the Cloud Function with proper roles assigned to initialize the BigQuery client. Provided that API Gateway is providing authorization to reach your Cloud Function, there is no need for the extra layer of impersonation.
Another option is to store the matching service account JSON key file in Secret Manager and pull it when required to create the BigQuery Client.
I've looked around for this but can't seem to find a canonical answer. I'm trying to follow best practices. Maybe I'm thinking of it the wrong way.
I'm thinking of my API users as two different types: developers and end users, with separate django models in separate applications.
Developers will build clients for the API, and have access to certain resources of the API without the need of users login in. To limit their access, I would require them to register and in exchange give them an API key. We would also dogfood this to say, build a site frontend using Angular and iOS app.
Once those developers build their API clients, users of my site, which have already created a user account, will use the API clients created by developers. In the request from those clients I would expect a developer name, api_key as well as username/password (digest, if its our own trusted client and oauth token for thid party developers). This will require to check 1) developers are allowed to use the API by checking their APIKey, and 2) authenticate the end user. Is this possible in tastypie?
Am I going about this the wrong way? How would I do the double authentication?
We run a production site with this exact scheme. Of course you'll have to do your own tunning. But the general idea is good. You could have some OAuth inplace too, but we've found that it's not worth it. OAuth is too complicated for most cases.
I'll explain roughly what we do.
This is the App/developers part:
We identify "apps" (iOS, Android, BB, the site). Each app has an ApiClient instance model. The ApiClient has three attrs: name, public key, and private key.
We exchange the public and private keys through a safe channel with the ApiClient owner (the app).
The app must send every request indicating the public key and a signature generated with the private key (using hmac).
Everytime we get a request, we get the public key from the request, look it up in the DB, see what App it belongs too (the name) and check the signature. If everything is ok, the request is fulfilled.
For the user authentication part:
To authenticate a user we use other model ApiKey (provided by tastypie). Each user has an ApiKey. That model stores a unique (we could say random) string. When the user gets to the app he/she logs in into your API. The app should issue a request similar to this one:
POST /api/v1/login/
{
'username': 'XXX',
'password': 'XXX'
}
(please note that it always need to pass the previous public/private key auth)
If the user provided the right credentials we return an ApiKey unique key.
Every following request made by the app in behave of that user must include the key. That's the way you identify which user is trying to do each action.
An example of this last part:
User Jon logs in in the iOS app. (using regular username and password)
The app sends the request:
POST /api/v1/login/
{
'username': 'jon',
'password': 'snow'
}
We have a login API method. We check if the user exists and if the pass is ok. Suppose it's ok.
We sent the ApiKey info:
200 OK
{
'username': 'jon',
'key': '$123$'
}
The app has authenticated the user. It needs to use those credentials.
The user tries to do something in your app. Suppose he tries to get the datetime from your app. The app will issue this request:
GET /api/v1/date/
Authorization: ApiKey jon:$123$
That's it. It's not super safe. The ApiKeys are not invalidated. But that's because we create our own internal Apps. It's worth to note that we borrow some stuff from Tastypie from this. Check this out: http://django-tastypie.readthedocs.org/en/latest/authentication.html#apikeyauthentication
This is tangental, but you may want to check out drf-keypair-permissions.
It's a Django module that uses asymmetric public/private keypairs to sign and verify the HTTP request using pre-shared keys. It can pair each public key with a user so the authorization doubles as a login, and can be extended to manage API throttling.
It supports a few algorithms, including RSA-SHA and elliptic curve, and keys can be managed in the admin area.
It uses the IETC Cavage-12 draft standard for processing the Authorization signature
What I'm trying to do:
Allow users that are authenticated and authorized on my site to use a form that allows them to create events on my calendar using Google Calendar's API.
I was able to accomplish this through Google Calendar API v2 by manually authenticating my Google account by doing this:
calendar_service = gdata.calendar.service.CalendarService()
calendar_service.email = 'email#site.com'
calendar_service.password = 'password'
calendar_service.source = 'Calendar'
calendar_service.ProgrammaticLogin()
I could then pipe in the POST date from my site's form and create an event using the API, but I'm at a loss for how to do this with V3 of the API.
I have read the documentation and tried implementing:
created_event = service.events().insert(calendarId='primary', body=event).execute()
But this requires that a service object be created, which in turn requires account authentication through OAuth 2.0, which is not what I'm trying to accomplish. I need to manually authenticate my own calendar instead of asking a user if my application can access their calendar.
I'm sure I'm missing some fundamental logic or making some false assumptions. Any help in the right direction would be appreciated.
You have to use OAuth to authorize your code to access your own calendar; once you do that, you'll get a token that you can keep. When your code runs again, it should reuse the token to connect to your calendar. Users won't be challenged, but the token might be refreshed from time to time.
Can't suggest any code because I personally hate OAuth with a passion :(
#Ben - The service object can be created either with a key/secret combination OR using a Service Account where you use a email/cert combination and the prn parameter to identify the user on which you want to operate. Both methods require registration in your API console.
Using a Service Account, you could just specify your email address in the prn parameter to insert the event into your calendar. The insert statement stays the same since you're operating on your account with this specification.
credentials = SignedJwtAssertionCredentials(SERVICE_ACCOUNT_EMAIL, key,
scope='https://www.googleapis.com/auth/calendar', prn='you#domain.com')
...
created_event = service.events().insert(calendarId='primary', body=event).execute()
Here's an example of that: https://developers.google.com/drive/delegation