OAuth "401: Invalid Credentials" after some period of time

12 views
Skip to first unread message

matoni via StackOverflow

unread,
Jan 24, 2015, 11:38:39 AM1/24/15
to google-appengin...@googlegroups.com

I have created simple app using Google Drive API with OAuth2 authentication based on this sample plus-appengine-sample

So, I have two servlets implementations: AbstractAppEngineAuthorizationCodeServlet and AbstractAppEngineAuthorizationCodeCallbackServlet which should do all the hard work for me (oauth work flow).

public class DriveServlet extends AbstractAppEngineAuthorizationCodeServlet {
    private static final String MY_APP_NAME = "Drive API demo";
    private static final long serialVersionUID = 1L;

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
        AuthorizationCodeFlow authFlow = initializeFlow();
        Credential credential = authFlow.loadCredential(getUserId(req));

        if (credential == null) {
            resp.sendRedirect(authFlow.newAuthorizationUrl()
                    .setRedirectUri(OAuthUtils.getRedirectUri(req)).build());
            return;
        }

        Drive drive = new Drive.Builder(OAuthUtils.HTTP_TRANSPORT_REQUEST, 
                OAuthUtils.JSON_FACTORY, credential).setApplicationName(MY_APP_NAME).build();

        // API calls (examines drive structure)
        DriveMiner miner = new DriveMiner(drive);
        req.setAttribute("miner", miner);

        RequestDispatcher view = req.getRequestDispatcher("/Drive.jsp");
        view.forward(req, resp);
    }

    @Override
    protected AuthorizationCodeFlow initializeFlow() throws ServletException, IOException {
        return OAuthUtils.initializeFlow();
    }

    @Override
    protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
        return OAuthUtils.getRedirectUri(req);
    }
}

public class OAuthCallbackServlet extends AbstractAppEngineAuthorizationCodeCallbackServlet {
    private static final long serialVersionUID = 1L;

    @Override
    protected AuthorizationCodeFlow initializeFlow() throws ServletException, IOException {
        return OAuthUtils.initializeFlow();
    }

    @Override
    protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
        return OAuthUtils.getRedirectUri(req);
    }

    @Override
    protected void onSuccess(HttpServletRequest req, HttpServletResponse resp, 
            Credential credential) throws ServletException, IOException {
        resp.sendRedirect(OAuthUtils.MAIN_SERVLET_PATH);
    }

    @Override
    protected void onError(HttpServletRequest req, HttpServletResponse resp, 
            AuthorizationCodeResponseUrl errorResponse) throws ServletException, IOException {
        String nickname = UserServiceFactory.getUserService().getCurrentUser().getNickname();
        resp.getWriter().print(
                "<h3>I am sorry" + nickname+ ", an internal server error occured. Try it later.</h1>");
        resp.setStatus(500);
        resp.addHeader("Content-Type", "text/html");
        return;
    }
}

public class OAuthUtils {
    private static final String CLIENT_SECRETS_FILE_PATH = "/client_secrets.json"; 
    static final JacksonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
    static final UrlFetchTransport HTTP_TRANSPORT_REQUEST = new UrlFetchTransport();
    private static final Set<String> PERMISSION_SCOPES = Collections.singleton(DriveScopes.DRIVE_READONLY);
    private static final AppEngineDataStoreFactory DATA_STORE_FACTORY = AppEngineDataStoreFactory.getDefaultInstance();
    private static final String AUTH_CALLBACK_SERVLET_PATH = "/oauth2callback";
    static final String MAIN_SERVLET_PATH = "/drive";

    private static GoogleClientSecrets clientSecrets = null;

    private OAuthUtils() {}

    private static GoogleClientSecrets getClientSecrets() throws IOException {
        if (clientSecrets == null) {
            InputStream jsonStream = OAuthUtils.class.getResourceAsStream(CLIENT_SECRETS_FILE_PATH);
            InputStreamReader  jsonReader = new InputStreamReader(jsonStream);
            clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, jsonReader);
        }
        return clientSecrets;
    }

    static GoogleAuthorizationCodeFlow initializeFlow() throws IOException {
        return new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT_REQUEST,
                JSON_FACTORY, getClientSecrets(), PERMISSION_SCOPES)
                .setDataStoreFactory(DATA_STORE_FACTORY)
                .setAccessType("offline").build(); 
    }

    static String getRedirectUri(HttpServletRequest req) {
        GenericUrl requestUrl = new GenericUrl(req.getRequestURL().toString());
        requestUrl.setRawPath(AUTH_CALLBACK_SERVLET_PATH);
        return requestUrl.build();
    }
}

Authentication flow works as expected as well as Drive API calls, but somehow, after some period of time, I'm getting this exception on refresh:

Uncaught exception from servlet
        com.google.api.client.googleapis.json.GoogleJsonResponseException: 401
        {
        "code" : 401,
        "errors" : [{ "domain" : "global", 
                      "location" : "Authorization", 
                      "locationType" : "header", 
                      "message" : "Invalid Credentials", 
                      "reason" : "authError" }],
        "message" : "Invalid Credentials"
        }
        at com.google.api.client.googleapis.json.GoogleJsonResponseException.from(GoogleJsonResponseException.java:145)
        at com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:113)
        at com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:40)
        at com.google.api.client.googleapis.services.AbstractGoogleClientRequest$1.interceptResponse(AbstractGoogleClientRequest.java:312)
        at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:1049)
        at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:410)
        at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:343)
        at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.execute(AbstractGoogleClientRequest.java:460)
        at sk.ennova.teamscom.drive.DriveMiner.getRootFolderId(DriveMiner.java:46)
        at org.apache.jsp.Drive_jsp._jspService(Drive_jsp.java:61)

It seems that token has expired, but isn't it a work for servlets to request a new access token with the refresh token which they stored? I use offline access type, so refresh token should be delivered to callback servlet at first request.

Here "401 Unauthorized" when trying to watch changes on Google Drive with Java API Client are some hints where could be the problem, but handling token expiration should not be my case if I'm using these servlets (correct me if I am wrong). Also scope DriveScopes.DRIVE_READONLY seems OK for reading "drive" tree structure (get files of given folder and so on). Where could be the problem?



Please DO NOT REPLY directly to this email but go to StackOverflow:
http://stackoverflow.com/questions/28127680/oauth-401-invalid-credentials-after-some-period-of-time

Price via StackOverflow

unread,
Jan 24, 2015, 11:58:39 AM1/24/15
to google-appengin...@googlegroups.com

You need to first specify that you need a refresh token for offline / long term access and then save the refresh token for later use when the access token expires. You can request a new access token using the refresh token until the user revokes your access to her account. See the official documentation here:

https://developers.google.com/accounts/docs/OAuth2WebServer#refresh



Please DO NOT REPLY directly to this email but go to StackOverflow:
http://stackoverflow.com/questions/28127680/oauth-401-invalid-credentials-after-some-period-of-time/28127895#28127895
Reply all
Reply to author
Forward
0 new messages