I have an IdentityServer4 identity provider server. For the most part, I am using the template code from their repo. I am trying to add Google sign-in. I configured the GoogleSignIn in startup and added ClientId/ClientSecret.
When I don't configure the return URIs in the GCP project I get the following error from Google:
"The redirect URI in the request, https://localhost:44333/signin-google, does not match the ones authorized for the OAuth client. To update the authorized redirect URIs..."
When I add the URI
Then as soon as I call Challenge I immediately get a failed callback from Google.
[HttpGet]
public async Task<IActionResult> Callback()
{
var result = await HttpContext.AuthenticateAsync(IdentityConstants.ExternalScheme);
// Here, result.Succeeded is false
// Rest of the method...
}
What could be the problem?
If you have made it to the Callback method, then it sounds like Google auth has completed successfully. However, since the IdentityConstants.ExternalScheme cookie is not present, it sounds like you might have a little misconfiguration.
Once the Google authentication handler has completed, it will sign in using the auth scheme set in its SignInScheme property or the default sign-in scheme. It stores the claims from Google into a local auth method, such as a cookie.
What scheme is the Google authentication handler configured to use? If you're using the quickstarts, it may be using IdentityServerConstants.ExternalCookieAuthenticationScheme rather than ASP.NET Identity's IdentityConstants.ExternalScheme that you are looking for.
Related
I have a web application where users can sign in with Google.
To the sign-in process, I add a scope to be able to access Google Calendar.
Now that the user is signed in, I would like to - in server-side - get their current Google access token in order to make a request and get a list of their events.
Is there a way to get the current OAuth token (no need for refresh token) in order for me to make this completely on the server-side?
I'd say that you can check this article and put special attention to the recommendation for websites.
I understand you have configured already the consent screen, which is the first step of the basic steps on using OAuth 2.0. So I understand that you only have to perform the following steps:
Obtain an access token from the Google Authorization Server
Examine scopes of access granted by the user.
Send the access token to an API
I think you can also give a look to this other doc for more GCP insights over your goal to authorize the request using user tokens
Edited:
Regarding the Firebase Authentication, I understand this happens at the user's device, and you could use some code to retrieve the token and then send it to your back end servers as mentioned in here.
As a sample here there's the sample code for retrieving the token in Android:
FirebaseUser mUser = FirebaseAuth.getInstance().getCurrentUser();
mUser.getIdToken(true)
.addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
public void onComplete(#NonNull Task<GetTokenResult> task) {
if (task.isSuccessful()) {
String idToken = task.getResult().getToken();
// Send token to your backend via HTTPS
// ...
} else {
// Handle error -> task.getException();
}
}
});
A little about OAuth 2.0
Whenever a user signs up to your app/website via Google or 3rd Party, an Authorization Code, this Authorization Code is exchanged for an AccessToken & RefreshToken.
The AccessToken sent via Google are valid generally for 60 minutes.
Offline Access (Server Side)
Let's break it down to two parts:
If your need to update within 60 minutes of user's last activity
You can use firebase along with gapi to achieve that. You'll be provided with the AccessToken that can be sent back to server to add to calendar.
More info on implementation
If you need to update after 60 minutes of user's last activity
Firebase & gapi's most method handle the AuthorizationCode flow internally. They even further refresh the AccessToken after 60 minutes. This is beneficial for most developers as they won't have a headache of managing all the tokens.
This method but, hides RefreshToken & AuthorizationCode from the developer. That is even if your server has the access token, it won't be able to refresh it and it would be deemed useless.
To achieve complete offline access, in the initial request to get AuthorizationCode you will need to send a HTTP GET parameter access_type to offline
GAPI provides you with grantOfflineAccess() method which returns the AuthorizationCode that can be later used on your server to fetch access token & refresh token.
Note: If you are storing AuthorizationCode in your database, make sure it is secure. The limitation in Firebase are set due to security reason. It is more secure to not talk with AuthorizationCode generally.
More links
https://developers.google.com/identity/protocols/oauth2/web-server
https://developers.google.com/identity/sign-in/web/reference
https://developers.google.com/identity/sign-in/web/server-side-flow
https://developers.google.com/identity/sign-in/web/backend-auth
Retrieve Google Access Token after authenticated using Firebase Authentication
In the old version of Firebase, my server app written in Java would authenticate with my backend using the secret and the JWT token generator. Then, at anytime, I could call Firebase.getAuth().getToken() and reuse that token in an HTTP call as the auth parameter.
With the new firebase-server-sdk, how would I reuse my limited service account credentials / token with the REST API?
Map<String, Object> auth = new HashMap<String, Object>();
auth.put("uid", "server-app");
FirebaseOptions options = new FirebaseOptions.Builder()
.setDatabaseUrl(Environment.FIREBASE_URL)
.setServiceAccount(MyClass.class.getResourceAsStream("/keys/dev.json"))
.setDatabaseAuthVariableOverride(auth)
.build();
FirebaseApp.initializeApp(options);
That all works great when I use the SDK to subscribe / write to certain locations - specifically locations that require a server-app uid. But I use REST in conjunction in my server app, because I want my server app to make synchronous reads, something Firebase only supports through the REST API.
FirebaseRestClient firebaseRest = new RestAdapter.Builder()
.setEndpoint(Environment.FIREBASE_URL)
.setRequestInterceptor(new RequestInterceptor() {
#Override
public void intercept(final RequestFacade request) {
request.addQueryParam("access_token", FirebaseAuth.getInstance().createCustomToken("server-app"));
}
})
.build().create(FirebaseRestClient.class);
I've tried adding both the access_token and auth param. It seems like that createCustomToken method produces a valid JWT, but the REST API isn't responding.
When I pass in that createCustomToken return value as the auth param, I get the following message:
"error" : "Missing claim 'kid' in auth header."
When I pass in that createCustomToken return value as the access_token param, I get the basic Permission denied API response.
Is there an easy way to reuse my existing firebase-server-sdk credentials in a REST API call?
The token you're attempting to use is a Firebase Authentication ID token - the type which is designed to be passed to the Firebase SDK on a client. The REST API accepts a Firebase access token (just like the ones in previous Firebase clients).
Your authentication is failing because normally the Firebase SDK takes care of turning your ID token into an access token. Your server can not do this transition or generate an access token using the Firebase SDK so I recommend using the original Firebase Token Generator library with your Firebase Secret to create access tokens for the REST API. This will work fine even for new Firebase projects created since the I/O release.
Note: In the Console your Database Secret can be found under (Gear Icon) > Project Settings > Database.
I'm sure this is not advised, given the name of the undocumented property, but...
If your SDK has logged in with a serviceAccount, you can use firebase.auth().INTERNAL.getToken(), which returns a promised accessToken (and expirationTime) which then works with the ?access_token parameter.
I am using Visual Studio 2015 Enterprise and ASP.NET vNext Beta8 to issue and consume JWT tokens as described here.
In our implementation we're storing some client details in Redis at token issuing time and we would like the flush this information when the user logs out.
My question is what is the best practices for logging out with OIDC?
While I could roll my own contoller for this purpose I couldn't help but notice Open ID Connect (OIDC) seems somewhat primed to handle this case. For example OIDC has an OnLogoutEndpoint handler and LogoutEndpointPath settings. But when I call the OIDC logout URI that handler appears to accept any random x-www-form-urlencoded form I throw at it and doesn't in any particular way seem to be demanding the presence of a token.
Any advice on proper OIDC logout practices would be very much appreciated.
In AspNet.Security.OpenIdConnect.Server, the logic used for the logout endpoint is left as an exercise.
In this sample, it is implemented using an MVC 6 controller, where you're - of course - free to add custom logic to remove cached details from your Redis server.
[HttpPost("~/connect/logout")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout() {
// When invoked, the logout endpoint might receive an unauthenticated request if the server cookie has expired.
// When the client application sends an id_token_hint parameter, the corresponding identity can be retrieved using AuthenticateAsync.
var identity = await HttpContext.Authentication.AuthenticateAsync(OpenIdConnectServerDefaults.AuthenticationScheme);
// Remove the cached details here. If you need to determine
// who's the authenticated user, you can use the identity variable.
// Remove the authentication cookie and return the user to the client application.
return SignOut("ServerCookie", OpenIdConnectServerDefaults.AuthenticationScheme);
}
You can also do something similar directly from the LogoutEndpoint event. Don't forget to call context.HandleResponse() to make sure the request is not intercepted by another middleware.
Am working on LTI Tool Provider, I have implemented an LTI auth package and am successfully able to get two (..one?) legged OAuth working aka match the signatures and we're all good to redirect, except one important thing.
What I want to be able to do, is
If this user doesnt exist, create it and log in
If the user exists, log our user in
Right now I have no way of actually determining my user once I redirect to a client route.
The LTI Consumer points to my Iron Router server route that looks something like:
Router.route('/lti', { where: 'server' }).post(function() {
provider.valid_request(request, function(error, valid) {
if (valid) {
this.response.writeHead(302, { Location: '/' });
} else {
this.response.writeHead(403);
}
});
return this.response.end();
});
Are there any packages I can use to get this working simply? Can I use something like accounts-base? Do I need to implement my own logic?
Any help or direction is appreciated.
Cheers.
I solved this by implementing a single-use authentication token system, handled by a custom login handler using accounts-base and the Accounts.registerLoginHandler method.
Rough auth flow overview:
LTI Route (server)
If authenticated, create a new account/update the old one
Insert a token + timestamp object into a collection.
Redirect to an authentication route, passing our token as a parameter
Auth route (client)
Check if our user is logged in. If so, redirect to our home route
If a token has been provided and it exists, mark it used. As we have no way of checking for a user in a server route, if a user has a session, closed and opens the link through the LMS again, we need to deal with our excess tokens.
If our user is not logged in, check for a token. If it exists, pass it to a custom authentication via Accounts.callLoginMethod
Our custom login handler validates our token. If legitimate, consume the token and log the user in.
My code is messy, but when I refactor I'll probably open source it as a Meteor package.
We are using firebase with google authentication. We chose Google because our application makes Google API calls. We authorize these api calls with the access_token included in authorization payload that is returned from firebase. However, we are having trouble figuring out how to refresh the access_token after it expires. According to Google, we should assume the access_token may expire for various reasons.
Therefore, (as I understand it) we need a way to refresh this token without forcing the user to reauthorize. Ideally, I could request the offline access_type when requesting the firebase auth...but I dont see how to do that (short of triggering firebase.authWithOAuthPopup(...) again, which we absolutely do not want to do as the users session is obviously still valid.
Is it possible to get an offline access_type Google oauth token through Firebase so that Google will return a refresh_token (https://developers.google.com/accounts/docs/OAuth2WebServer#formingtheurl)? With a refresh_token, I think I can grab a new access_token for api calls.
I was trying this but its definitely not supported:
this.firebase.authWithOAuthPopup("google", this.authenticateGoogle.bind(this), {
access_type: 'offline', <-- not passed to Google
scope: 'https://www.googleapis.com/auth/userinfo.profile, https://www.googleapis.com/auth/devstorage.read_write'
});
All calls to https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=abcd show the access_type as online.
Thanks
A solution that minimizes server side implementation requirements.
TL:DR; Use the Google Sign-In for Websites library to generate the auth credentials. Login Firebase using the auth credentials, and post the offline access exchange code to your server.
Client Side
Client side I have implemented Google Sign-In for Websites by including the following :
<script src="https://apis.google.com/js/platform.js?onload=loadAuth2" async defer></script>
<script>
function loadAuth2 () {
gapi.load('auth2', function() {
gapi.auth2.init({
client_id: 'your firebase Web client ID',
cookie_policy: 'single_host_origin',
scope: 'profile ...'
});
});
}
</script>
Note: Scope should be a space delimited list of the access scopes you require.
Assuming Firebase is loaded my login click handler is :
<script>
function login() {
const auth = gapi.auth2.getAuthInstance();
auth.then(() => {
auth.grantOfflineAccess({
'redirect_uri': 'postmessage',
'prompt': 'concent',
'approval_prompt': 'force',
}).then(offlineAccessExchangeCode => {
// send offline access exchange code to server ...
const authResp = auth.currentUser.get().getAuthResponse();
const credential = firebase.auth.GoogleAuthProvider.credential(authResp.id_token);
return firebase.auth().signInWithCredential(credential);
}).then(user => {
// do the thing kid!
});
});
}
</script>
Calling auth.grantOfflineAccess with 'redirect_uri': 'postmessage' causes the Google auth2 library to communicate the authentication credentials back to your web app via window.postMessage. See here for the auth2 library reference.
Elsewhere in my application I am listening for Firebase auth state to change.
firebase.auth().onAuthStateChanged(user => {
if (user) {
// navigate to logged in state
} else {
// navigate to login page
}
});
Server Side
I POST the offlineAccessExchangeCode (which looks like {"code": "..."}) to my server to exchange for a creds for the currently authenticated user, which includes a refresh token. Though client side you can access firebase.auth().currentUser.refreshToken this token was not working for me (maybe someone can tell me I was mistaken here :D)
My server side code in Python follows. Please note that the Google SDKs are auto-generated for most Google services, so the following code should translate easily into to any language they support.
from oauth2client import client
// ...
// assuming flask
#app.route("/google/auth/exchange", methods=['POST'])
def google_auth_exchange():
auth_code = request.get_json()['code']
credentials = client.credentials_from_clientsecrets_and_code(
'config/client_secret.json', ['profile', '...'], auth_code)
print(credentials.refresh_token)
And that's pretty much it. I would assume that you have a server or some server side code if you require offline access so hopefully implementing a route isn't too far from an ideal solution.
Sequencing
Note : The GCLID Resolver is a project I am currently working on that required this.
SOLVED for now. According to Rob DiMarco from Firebase: "Unfortunately, it is not currently possible to get a Google OAuth refresh token via Firebase, though it's something we're aware of and hope to fix."
Use a different OAuth 2.0 library in your client code that is able to send an authorization request with the access_type=offline. There's nothing that is firebase specific in the OAuth 2.0 interaction with Google that gets you an access token and a refresh token, so you could rely on separate code for that part. Of course you'll need to provide scope(s) specifically for Firebase (I believe at least "https://www.googleapis.com/auth/freebase") but that's not a problem for any OAuth 2.0 client library.
Solved: Google OAuth Refresh Tokens not returning Valid Access Tokens
You have to handle authentication on a server, then return an idtoken to the client and sign in with firebase after being authenticated on the server. That way you can get refresh tokens on the backend, store them on the user on your database (from the server) and use that refresh token to reauthenticate.
2023 Update: This is now possible! If you follow the instructions here:
https://firebase.google.com/docs/auth/extend-with-blocking-functions#accessing_a_users_identity_provider_oauth_credentials
To create a blocking function, you can get a refresh token. See example code below:
exports.beforeCreate = authClient.functions().beforeCreateHandler((user, context) => {
if (context.credential &&
context.credential.providerId === 'google.com') {
const refreshToken = context.credential.refreshToken;
const uid = user.uid;
// These will only be returned if refresh tokens credentials are included
// (enabled by Cloud console).
// TODO: Store or use your refreshToken here!
}
});
Just make sure you register the blocking function after you've deployed it and make sure you select refreshToken :)
Credit: https://stackoverflow.com/a/74989323