AWS API credentials with OneLogin SAML and MFA - api

We want to allow our users to retrieve a set of temporary CLI credentials for a given AWS role by signing in to OneLogin with password and MFA. We have a working solution, but it requires the user to fully re-authenticate to OneLogin (including MFA) every 60 minutes as the AWS temporary credentials expire. I think that won't fly - our users are accustomed to permanent API credentials tied to a real IAM user.
Ideally, we'd like to allow users to authenticate once a day, securely cache the resulting SAML assertion, and use that to transparently refresh the AWS API credentials as needed. I'm thinking of something like aws-keychain that would use the local OS credential store to remember the SAML assertion, and only prompt the user for input when their OneLogin session has timed out.
This almost works as-is. The catch is that the SAML assertion returned by OneLogin's saml_assertion and verify_factor endpoints sets a three-minute deadline on the Subject and Conditions fields.
Is there a way to do what we want, or are we trying to route around a core SAML principle?

The accepted answer here is no longer true. It is now possible to authenticate the user and verify MFA once at the start of a session and then have the session refreshed on an hourly basis without having to enter further MFA tokens.
To do this you must use the --loop parameter of the CLI tool and have a corresponding App Policy in OneLogin that enables the "Skip if OTP received within last X minutes" setting.
https://developers.onelogin.com/api-docs/1/samples/aws-cli

We're in the process of officially adding an option to in our official CLI tool to re-use the user credentials similarly to what you're describing.
Essentially our CLI tool has an option to reuse the user/pass in the background to continuously refresh the access info in a user profile so while the user will provide credentials once, the access info will get updated before it expires.
But unfortunately, at least at this time, there's no way to get around the MFA part if the application policy requires MFA. It'll need that MFA to refresh the credentials.
If you have to have MFA, you can always use the OneLogin MFA APIs to bake in an MFA flow into the tool...
Check it out here: https://github.com/onelogin/onelogin-aws-cli-assume-role/pull/5

Related

What's the proper way to implement a "static" authorization token feature using OIDC (or OAuth2)

I am exploring possible solutions for creating something like "API Keys" to consume my API. The goal is to allow for users to generate one or many "API Keys" from the web app and use the static generated key from the CLI app.
The web app and the client app are already using standard OIDC with JWT tokens for authentication and authorization using RBAC (role-based access control). The CLI app can already authenticate the user through the standard browser flow (redirects the user to the browser to authenticate and exchange the token back to the client).
The "API Keys" solution I am trying to achieve should have some fine-grained options where it won't authenticate as the user, but will authorize the client on behalf of the user (something like the GitHub Personal Access Token).
To me it seems like a "solved problem" as multiple services provide this kind of feature and my goal is to do it the most standard way possible using the Oauth2/OIDC protocols but I can't find details on what parts of the protocols should be used.
Can anybody provide any guidance on how it is supposed to be done using the Oauth2/OIDC entities?
Can I achieve it by only using Role-based access control or do I need Resource-based access control?
It went through the path of creating a new client for each "API Key" created, but it didn't feel right to create so many clients in the realm.
Any guidance or links to any materials are appreciated.
Can anybody provide any guidance on how it is supposed to be done
using the Oauth2/OIDC entities?
OIDC is based on OAUth 2.0 so after user login you have id tokens, access token and refresh token on the backend side. To generate new access token without asking user for authentication data you should use refresh token: https://oauth.net/2/refresh-tokens/
Can I achieve it by only using Role-based access control or do I need
Resource-based access control?
resource-based access control is more flexible solution here, but if you business requirement is not complex, then role based might be enough.
It went through the path of creating a new client for each "API Key"
created, but it didn't feel right to create so many clients in the
realm.
It is one application so you should use one client with specific configuration for access token and roles/permissions for users.
Update:
We can use GitHub as an example:
User is authenticated during login
for OIDC code is exchanged for id token, access token and refresh token
session for user is set for web browser
User can request access token
in GitHub authenticated user can request github.com/settings/personal-access-tokens/new endpoint
request is accepted, because user is authenticated based on session
backend service responsible for returning access token can obtain new access token using refresh token from point 1.
access token is returned to GitHub user
To call your API in an OAuth way, CLI users must authenticate periodically. Resulting access tokens can be long lived, as for GitHub keys, if you judge that secure enough. The access token returned can be used exactly like an API key. There may be a little friction here between usability and security.
CONSOLE FLOW
The classic flow for a console app is to use the Native Apps Desktop Flow from RFC8252. This involves the user interactively signing in using the code flow, then receiving the response on a loopback URL. It is an interactive experience, but should only be required occasionally, as for GitHub tokens.
API KEYS
The access token returned is sent in the authorization header and you can use it as an API key. Access tokens can use a reference token format. to make them shorter and confidential, to prevent information disclosure. These will be more natural in a CLI.
API AUTHORIZATION
When your API is called, it must receive access tokens containing scopes and claims, to identify the user. This will enable you to authorize correctly and lock down permissions.
{
sub: 586368,
scope: repos_write,
topic: mobile,
subscription_level: silver
exp: ?
}
TOKEN REFRESH
Sometimes CLI access tokens are long lived, for convenience. A more secure option is for the CLI to use token refresh. It can then store a refresh token in OS secure storage, then renew access tokens seamlessly. My blog post has some screenshots on how this looks, and a desktop app that does not require login upon restart. The CLI needs to deal with expired access tokens and handle 401 responses.
DYNAMIC CLIENT REGISTRATION
Some developer portal scenarios use DCR. It is another option in your security toolbox. It could potentially enable a silent client per CLI user:
User runs a standard authentication flow with a DCR scope
This returns an access token that enables client registration
The resulting token is used to register a new client
This could potentially be a client ID and client secret used in a CLI
Afterwards, the user and client are bound together. Probably not immediately relevant, but worth knowing about.

Authenticating People API without UI

I want to authenticate personal google account for people API to send get/post requests,
However, I have to avoid authenticating for API each time as there will a cron job using the API. One way is to authenticate the first time and then use the refresh token for the necessary requests until the account revokes access (by changing password or manually removing the permission console)
Could there be a way, such that I could avoid manual authentication completely since the cron job will run on server-side
Used Service account for the same but didn't get required results
See https://developers.google.com/identity/protocols/oauth2/web-server
You can give your application consent once (the first time), and set in the authorization parameters the optional access_type='offline'.
"Set the value to offline if your application needs to refresh access tokens when the user is not present at the browser. This is the method of refreshing access tokens described later in this document. This value instructs the Google authorization server to return a refresh token and an access token the first time that your application exchanges an authorization code for tokens."

How to use Google Identity to log in from multiple devices?

How can I use Google Identity platform as a login / registration system for my own service?
Specifically - how can I do this and support login for same user from different devices?
Using for web service, nodejs without npm modules: passportjs / googleapis / google-auth-library.
My idea:
User opens myClientApp/login page and clicks on GoogleLogIn button which will ask him to authorize my service for specific scopes of his Google account.
I then get the refresh token & access token and save it in DB, then send the refresh token to the client with cookie.
Whenever I make a call to my own service API I send the refresh token from the cookie.
As long as I have valid access token saved in my DB or the refresh token is not expired - I treat the user matching that refresh token as an active session for my service.
Security problems:
cookies attacks, and the refresh token is easily accessed from the browser. Could use https / encryption and more methods to secure the cookie and it's value. Still- someone could copy the cookie from one computer to another!
Multiple login problems:
If the user login on different device, a new refresh token will be created. The previous device the user logged in to will now hold a wrong refresh token in the cookie...
Could the OpenID solve this? Do I need to use JWT?
What is the most secure way to use Google Identity login in my own service while supporting multiple devices login for the same user?
First, make sure that you really understand the security implications for what you want to do.
For example, NEVER send the Refresh Token to a client.
If you want to use the same tokens for the same client on multiple devices, you have a chicken and egg situation. How do you "authenticate" the user at each device. How do you know that user "John" is actually user "John" but on a different device the first time?
Your goal is not to trade convenience for less security. Your goal should always be security first, no matter the inconvenience.
A better approach is to let Google authenticate and authorize a user on each device. They only have to do this once per device. Your backend systems keep track of the Refresh Token issued for each device. You can then generate the Access Tokens and Identity Tokens when needed - they expire after one hour anyways. Store a cookie on the user's device that identifies them to your system so that you can look up who they are, get the Refresh Token, create new Access Tokens, etc.
There is a limit to the number of Refresh Tokens that can be issued before the oldest ones are voided. I think the number is 50. This is usually not a problem. If a Refresh Token is invalid, just put the user back thru the authenticate process and store the new token.
Also provide the user with a sign-out method that removes all stored tokens in your system.
The cookie that you store on the client's devices should be opaque meaning that there is no stored information in the cookie and the cookie is only valid for that device and no other devices. This solves the stolen cookie moved to another device problem.
I will now touch on some of your questions:
My idea: User opens myClientApp/login page and clicks on GoogleLogIn
button which will ask him to authorize my service for specific scopes
of his Google account.
Google OAuth does not work that way. You send the user to a URL, Google manages all display and input with the end user. Once everything is complete a callback URL on your server is called and you are passed a code. The exact details depend on the type of OAuth Flow that you are using.
I then get the refresh token & access token and save it in DB, then
send the refresh token to the client with cookie.
During the OAuth Flow you will request the Access Token, Refresh Token and Identity Token. The Refresh Token is saved in the database. Never send this token to the client. Read my suggestion above about creating an opaque cookie that you send to the client.
Security problems: cookies attacks, and the refresh token is easily
accessed from the browser. Could use https / encryption and more
methods to secure the cookie and it's value. Still- someone could copy
the cookie from one computer to another!
Create an opaque cookie that is only valid for that device and no other devices. If a client sends you a cookie intended for a different device, consider this a problem and invalidate all cookies, tokens, etc for this user on all devices.
Multiple login problems: If the user login on different device, a new
refresh token will be created. The previous device the user logged in
to will now hold a wrong refresh token in the cookie...
I covered this issue above. Store the Refresh Token generated for each device in your DB. Consider each device / Refresh Token / cookie as a set.
Could the OpenID solve this? Do I need to use JWT? What is the most
secure way to use Google Identity login in my own service while
supporting multiple devices login for the same user?
By Open ID I think you mean Open ID Connect (OIDC). This is already integrated into Google OAuth and this is the part that generates the Identity Token.
Do I need to use JWT?
Google OAuth Tokens are generated from Signed JWTs. However for the most part you do not need to worry about the format of tokens. Google provides endpoints that validate and decode Google OAuth tokens.
What is the most secure way to use Google Identity login in my own
service while supporting multiple devices login for the same user?
I covered this question in the first part of my answer above.

AWS Cognito AdminUserGlobalSignOut does not invalidate Cognito cookie?

I am using the web UI forms provided by Cognito for user sign in to our site with an OAuth flow.
We have a use case where the user should be signed out of all logged in session across browsers.
Looking at the docs, it seems like AdminUserGlobalSignOut does exactly what we're after. However, after calling it the 'cognito' browser cookie saved for foo.auth.us-east-1.amazoncognito.com remains valid. The next time a user loads https://foo.auth.us-east-1.amazoncognito.com/oauth2/authorize they are forwarded to the callback url as if they're authenticated instead of being redirected to foo.auth.us-east-1.amazoncognito.com/login.
I assume this is because the cookie is not invalidated by calling AdminUserGlobalSignOut, however this seems like the exact purpose of this method.
Is this a bug or am I missing something required to log a user out globally?
I know it's kind of too late to answer, but I think this is due to the fact that Token and Cookie are independent of each other.
I think this is expected behavior because the AdminUserGlobalSignOut API is just a feature to revoke Refresh Token, not a feature to invalidate cookies issued by Cognito.
Also, as far as I know, there is no API to disable cookies issued by Cognito. I guess you have to use each Logout Endpoints.
Configuring a User Pool App Client - Amazon Cognito
https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-client-apps.html
Important
If you use Hosted UI and setup tokens less than an hour, the end user will be able to get new tokens based on their session cookie which is currently fixed at one hour.
AdminUserGlobalSignOut - Amazon Cognito Identity Provider
https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminUserGlobalSignOut.html
Signs out users from all devices, as an administrator. It also invalidates all refresh tokens issued to a user. The user's current access and Id tokens remain valid until their expiry. Access and Id tokens expire one hour after they are issued.
LOGOUT Endpoint - Amazon Cognito
https://docs.aws.amazon.com/cognito/latest/developerguide/logout-endpoint.html

How do I keep the user logged-in with Implicit flow?

From what I understand, the end-result of the implicit flow is the access token, which allows the client (in my case a JS SPA) to authenticate into resource servers (APIs).
The access token is usually only valid for ~1 hour, then it expires - making it useless.
What should my JS app do then? Redirecting the user back to the auth server is unrealistic since then the user will have to reenter their credentials every 1 hour!
I also know that the implicit flow doesn't support refresh tokens so I can't use those either.
Is there a way to persist the user's login? How do things like Facebook keep you logged-in indefinitely?
Just to clarify, you are asking about the Implicit flow which is detailed in the OAuth 2.0 RFC rather than OpenID Connect which deals more with authentication of a user?
With the implicit flow you do have to regularly call the authorisation endpoint to obtain a new token, but if the user remains logged into their identity provider then they should not be prompted to resubmit their credentials, and the token will be returned as a hash fragment in the redirect uri, with no user interaction required.
You can use an AJAX call to get the token on a back-channel so your SPA app user experience is not affected by the need to get new tokens.
To address the points you highlight in your question:
The access token is usually only valid for ~1 hour, then it expires -
making it useless.
Correct!
then the user will have to reenter their credentials every 1 hour!
Not necessarily.
If the user stays logged into the identity provider (e.g. facebook, google) then there will be a browser cookie between the user and that provider. This effectively means the identity provider does not need the user to re-enter credentials. The authorisation server should be able to return you a token with no interaction required.
Is there a way to persist the user's login?
You can't control it from your SPA. It's totally dependent on the user staying logged onto the identity provider. If they stay logged into facebook, google (or whatever IDP you app uses) then you should be able to get tokens non-interactively.
This article nicely explains how the implicit flow can be implemented.
If the session at the OP is still active (via a cookie perhaps), then OpenID Connect has a mechanism to refresh tokens in a (hidden) iframe: prompt=none.
According to the spec, when sending this flow...
The Authorization Server MUST NOT display any authentication or consent user interface pages. An error is returned if an End-User is not already authenticated or the Client does not have pre-configured consent for the requested Claims or does not fulfill other conditions for processing the request. The error code will typically be login_required, interaction_required, or another code defined in Section 3.1.2.6. This can be used as a method to check for existing authentication and/or consent.
prompt=none is also referred to from the Session Management specification.